Foundation

Tokens, motion presets and cn: the colour roles, radii, type scale, shadows and springs every pixilui item reads.

View as markdownFreeMITSince 0.1.0

Install#

npx shadcn@latest add @pixilui/foundation

Once per app, add the registry to components.json:

components.json
{
  "registries": {
    "@pixilui": "https://pixilui.com/r/{name}.json"
  }
}

Or install from the URL, no namespace needed: npx shadcn@latest add https://pixilui.com/r/foundation.json

Adds components/pixilui/foundation.css, components/pixilui/motion-tokens.ts, components/pixilui/lib/use-measure.ts, components/pixilui/lib/use-morph-size.ts, components/pixilui/lib/utils.ts. Needs clsx, tailwind-merge, motion.

Code#

/*
  pixilui foundation: every token the items read, and the Tailwind v4 mapping for them.

  How it is organised
  1. Raw tokens are CSS custom properties named --pixilui-*. Items never read them directly;
     they read the Tailwind utilities mapped in section 6. Theme by overriding --pixilui-*.
  2. Light is the default on :root. Dark is tuned separately (not inverted) and applies under
     [data-theme="dark"] or .dark, so it works with the docs site and with next-themes/shadcn apps.
  3. Accents: data-accent="neutral|violet|blue|green|amber|orange|coral|rose" on any element.
     Without it the house accent is neutral.
  4. Utilities keep the role names (bg-surface, text-secondary, border-border, rounded-control,
     h-control-md). Two roles are renamed in Tailwind because shadcn already uses the names for
     something else: the accent fill is bg-accent-solid (shadcn's bg-accent is a hover grey) and
     text on it is text-accent-contrast (shadcn's text-accent-foreground). Text colours use the
     --text-color-* namespace, so text-secondary and text-muted never repaint shadcn's
     bg-secondary or bg-muted. See docs/DECISIONS.md.

  Install: import once, after "tailwindcss", in the app's global stylesheet:
    @import "tailwindcss";
    @import "../components/pixilui/foundation.css";
*/

/* ------------------------------------------------------------------ 1. light (default) */
:root {
  color-scheme: light;

  /* surfaces */
  --pixilui-background: oklch(0.985 0 0);
  --pixilui-surface: oklch(1 0 0);
  --pixilui-surface-raised: oklch(1 0 0);
  --pixilui-surface-muted: oklch(0.97 0 0);
  --pixilui-surface-inverted: oklch(0.2 0 0);
  /* the bench a component sits on in previews and demos: page < field < surface */
  --pixilui-field: oklch(0.96 0 0);

  /* text: three even greys. foreground for objects, secondary for body, muted for meta only */
  --pixilui-foreground: oklch(0.18 0 0);
  --pixilui-text-secondary: oklch(0.475 0 0);
  --pixilui-text-muted: oklch(0.55 0 0);
  --pixilui-text-inverted: oklch(0.985 0 0);

  /* borders: translucent, so they sit right on any surface */
  --pixilui-border: oklch(0 0 0 / 0.08);
  --pixilui-border-subtle: oklch(0 0 0 / 0.05);
  --pixilui-border-strong: oklch(0 0 0 / 0.14);

  /* status: the base colour reads as text on a surface and as a fill under -foreground */
  --pixilui-success: oklch(0.53 0.13 150);
  --pixilui-success-subtle: oklch(0.962 0.032 150);
  --pixilui-success-foreground: oklch(0.99 0.01 150);
  --pixilui-warning: oklch(0.56 0.14 58);
  --pixilui-warning-subtle: oklch(0.966 0.04 80);
  --pixilui-warning-foreground: oklch(0.99 0.01 80);
  --pixilui-danger: oklch(0.55 0.2 25);
  --pixilui-danger-subtle: oklch(0.962 0.025 25);
  --pixilui-danger-foreground: oklch(0.99 0.01 25);
  --pixilui-info: oklch(0.55 0.16 250);
  --pixilui-info-subtle: oklch(0.962 0.025 250);
  --pixilui-info-foreground: oklch(0.99 0.01 250);

  /* controls (switch, slider, checkbox) */
  --pixilui-control-track: oklch(0.91 0 0);
  --pixilui-control-thumb: oklch(1 0 0);

  /* focus */
  --pixilui-focus-outline-width: 2px;
  --pixilui-focus-outline-offset: 2px;

  /* shadows: smooth layered stacks in three tiers */
  --pixilui-shadow-resting: 0 1px 1px oklch(0 0 0 / 3%), 0 1px 2px oklch(0 0 0 / 3%);
  --pixilui-shadow-raised: 0 1px 2px oklch(0 0 0 / 4%), 0 4px 8px -2px oklch(0 0 0 / 4%), 0 12px 20px -8px oklch(0 0 0 / 6%);
  --pixilui-shadow-floating: 0 1px 2px oklch(0 0 0 / 4%), 0 6px 12px -4px oklch(0 0 0 / 5%), 0 18px 32px -10px oklch(0 0 0 / 8%), 0 40px 64px -24px oklch(0 0 0 / 10%);
  /* the tactile pill: a micro two-layer shadow under the control, a light step inside its top edge */
  --pixilui-shadow-control: 0 1px 1px oklch(0 0 0 / 6%), 0 2px 4px -1px oklch(0 0 0 / 6%);
  --pixilui-highlight: inset 0 1px 0 oklch(1 0 0 / 22%);

  /* shape: radii nest control < panel < surface */
  --pixilui-radius-control: 18px;
  --pixilui-radius-panel: 26px;
  --pixilui-radius-surface: 34px;
  --pixilui-radius-pill: 9999px;
  --pixilui-control-height-sm: 36px;
  --pixilui-control-height-md: 44px;
  --pixilui-control-height-lg: 50px;

  /* type: Geist for display (30px and up), Inter for body, Geist Mono for numbers and code only */
  --pixilui-font-display: var(--font-geist-sans, "Geist"), "Inter", ui-sans-serif, system-ui, sans-serif;
  --pixilui-font-body: var(--font-inter, "Inter"), ui-sans-serif, system-ui, sans-serif;
  --pixilui-font-mono: var(--font-geist-mono, "Geist Mono"), ui-monospace, "SF Mono", Menlo, monospace;
  --pixilui-text-12: 0.75rem;
  --pixilui-text-14: 0.875rem;
  --pixilui-text-16: 1rem;
  --pixilui-text-18: 1.125rem;
  --pixilui-text-22: 1.375rem;
  --pixilui-text-28: 1.75rem;
  --pixilui-text-36: 2.25rem;
  --pixilui-text-52: 3.25rem;
  --pixilui-text-72: 4.5rem;
  --pixilui-leading-body: 1.4;
  --pixilui-leading-display: 1.1;
  --pixilui-tracking-body: -0.01em;
  --pixilui-tracking-display: -0.03em;
  --pixilui-weight-regular: 400;
  --pixilui-weight-medium: 500;

  /* space: 4px steps, identical to Tailwind's default spacing (p-4 is space-4) */
  --pixilui-space-1: 4px;
  --pixilui-space-2: 8px;
  --pixilui-space-3: 12px;
  --pixilui-space-4: 16px;
  --pixilui-space-5: 20px;
  --pixilui-space-6: 24px;
  --pixilui-space-8: 32px;
  --pixilui-space-10: 40px;
  --pixilui-space-12: 48px;
  --pixilui-space-16: 64px;
  --pixilui-space-20: 80px;
  --pixilui-space-24: 96px;

  /* motion: durations (GROUND-RULES section 4) */
  --pixilui-duration-instant: 120ms;
  --pixilui-duration-quick: 150ms;
  --pixilui-duration-fast: 250ms;
  --pixilui-duration-standard: 240ms;
  --pixilui-duration-medium: 350ms;
  --pixilui-duration-slow: 400ms;
  --pixilui-duration-considered: 480ms;
  --pixilui-duration-very-slow: 500ms;
  --pixilui-duration-spring: 580ms;
  --pixilui-stagger-char: 16ms;
  --pixilui-stagger-item: 35ms;
  --pixilui-stagger-word: 40ms;
  --pixilui-stagger-line: 80ms;

  /* motion: easings */
  --pixilui-ease-enter: cubic-bezier(0.16, 1, 0.3, 1);
  --pixilui-ease-smooth-out: cubic-bezier(0.22, 1, 0.36, 1);
  --pixilui-ease-exit: cubic-bezier(0.7, 0, 0.84, 0);
  --pixilui-ease-move: cubic-bezier(0.77, 0, 0.175, 1);
  --pixilui-ease-swap: cubic-bezier(0.2, 0, 0, 1);
  --pixilui-ease-linear: linear;
  --pixilui-ease-bounce: cubic-bezier(0.34, 1.36, 0.64, 1);
  /* CSS spring (bounce .15) for transform transitions; pair with --pixilui-duration-spring */
  --pixilui-ease-spring: linear(0, 0.0258, 0.09, 0.1763, 0.2732, 0.3724, 0.4683, 0.5573, 0.6376, 0.7082, 0.7689, 0.8202, 0.8628, 0.8976, 0.9256, 0.9476, 0.9648, 0.9778, 0.9875, 0.9945, 0.9994, 1.0026, 1.0047, 1.0058, 1.0062, 1.0062, 1.0059, 1.0055, 1.0049, 1.0043, 1.0036, 1.0031, 1.0025, 1.002, 1.0016, 1.0013, 1);

  /* motion: blur */
  --pixilui-blur-subtle: 2px;
  --pixilui-blur-soft: 4px;
  --pixilui-blur-text: 8px;
}

/* ------------------------------------------------------------------ 2. dark (tuned) */
[data-theme="dark"],
.dark {
  color-scheme: dark;

  --pixilui-background: oklch(0.165 0 0);
  --pixilui-surface: oklch(0.22 0 0);
  --pixilui-surface-raised: oklch(0.25 0 0);
  --pixilui-surface-muted: oklch(0.255 0 0);
  --pixilui-surface-inverted: oklch(0.96 0 0);
  --pixilui-field: oklch(0.195 0 0);

  --pixilui-foreground: oklch(0.96 0 0);
  --pixilui-text-secondary: oklch(0.72 0 0);
  --pixilui-text-muted: oklch(0.62 0 0);
  --pixilui-text-inverted: oklch(0.2 0 0);

  --pixilui-border: oklch(1 0 0 / 0.08);
  --pixilui-border-subtle: oklch(1 0 0 / 0.05);
  --pixilui-border-strong: oklch(1 0 0 / 0.14);

  --pixilui-success: oklch(0.74 0.15 152);
  --pixilui-success-subtle: oklch(0.29 0.05 152);
  --pixilui-success-foreground: oklch(0.2 0.03 152);
  --pixilui-warning: oklch(0.8 0.14 78);
  --pixilui-warning-subtle: oklch(0.3 0.05 78);
  --pixilui-warning-foreground: oklch(0.22 0.04 70);
  --pixilui-danger: oklch(0.7 0.18 24);
  --pixilui-danger-subtle: oklch(0.29 0.06 24);
  --pixilui-danger-foreground: oklch(0.19 0.03 24);
  --pixilui-info: oklch(0.74 0.13 248);
  --pixilui-info-subtle: oklch(0.29 0.05 248);
  --pixilui-info-foreground: oklch(0.2 0.03 248);

  --pixilui-control-track: oklch(0.33 0 0);
  --pixilui-control-thumb: oklch(0.98 0 0);

  --pixilui-shadow-resting: 0 1px 2px oklch(0 0 0 / 30%);
  --pixilui-shadow-raised: 0 1px 2px oklch(0 0 0 / 30%), 0 8px 20px -6px oklch(0 0 0 / 45%);
  --pixilui-shadow-floating: 0 1px 2px oklch(0 0 0 / 30%), 0 12px 24px -8px oklch(0 0 0 / 45%), 0 40px 64px -24px oklch(0 0 0 / 60%);
  --pixilui-shadow-control: 0 1px 1px oklch(0 0 0 / 30%), 0 2px 4px -1px oklch(0 0 0 / 30%);
  --pixilui-highlight: inset 0 1px 0 oklch(1 0 0 / 7%);
}

/* ------------------------------------------------------------------ 3. accents
   Each accent carries its light and dark values; the theme in force picks one where it is
   used (section 6), so an accent or theme set on any element, at any depth, resolves right.
   accent             the fill (primary buttons, selection, switch on)
   accent-strong      accent-coloured text and icons on surfaces
   accent-subtle      tinted backgrounds
   accent-foreground  text and icons on the fill
   Light fills keep L <= 0.56 where text on them is light, so white text clears 4.5:1.
   To brand an app, override --pixilui-accent (and friends) or the -light/-dark pairs. */
:root,
[data-theme="light"],
.light {
  --pixilui-dark: 0;
}
[data-theme="dark"],
.dark {
  --pixilui-dark: 1;
}

:root,
[data-accent="neutral"] {
  --pixilui-accent-light: oklch(0.2 0 0);
  --pixilui-accent-strong-light: oklch(0.2 0 0);
  --pixilui-accent-subtle-light: oklch(0.955 0 0);
  --pixilui-accent-foreground-light: oklch(0.985 0 0);
  --pixilui-accent-dark: oklch(0.94 0 0);
  --pixilui-accent-strong-dark: oklch(0.96 0 0);
  --pixilui-accent-subtle-dark: oklch(0.28 0 0);
  --pixilui-accent-foreground-dark: oklch(0.2 0 0);
}

[data-accent="violet"] {
  --pixilui-accent-light: oklch(0.54 0.21 290);
  --pixilui-accent-strong-light: oklch(0.5 0.2 290);
  --pixilui-accent-subtle-light: oklch(0.955 0.03 290);
  --pixilui-accent-foreground-light: oklch(0.99 0.01 290);
  --pixilui-accent-dark: oklch(0.56 0.2 290);
  --pixilui-accent-strong-dark: oklch(0.78 0.13 290);
  --pixilui-accent-subtle-dark: oklch(0.29 0.07 290);
  --pixilui-accent-foreground-dark: oklch(0.99 0.01 290);
}

[data-accent="blue"] {
  --pixilui-accent-light: oklch(0.56 0.225 263);
  --pixilui-accent-strong-light: oklch(0.5 0.21 263);
  --pixilui-accent-subtle-light: oklch(0.96 0.025 263);
  --pixilui-accent-foreground-light: oklch(0.99 0.01 263);
  --pixilui-accent-dark: oklch(0.56 0.2 263);
  --pixilui-accent-strong-dark: oklch(0.78 0.12 263);
  --pixilui-accent-subtle-dark: oklch(0.29 0.07 263);
  --pixilui-accent-foreground-dark: oklch(0.99 0.01 263);
}

[data-accent="green"] {
  --pixilui-accent-light: oklch(0.53 0.13 155);
  --pixilui-accent-strong-light: oklch(0.48 0.12 155);
  --pixilui-accent-subtle-light: oklch(0.958 0.035 155);
  --pixilui-accent-foreground-light: oklch(0.99 0.01 155);
  --pixilui-accent-dark: oklch(0.72 0.15 155);
  --pixilui-accent-strong-dark: oklch(0.78 0.14 155);
  --pixilui-accent-subtle-dark: oklch(0.29 0.05 155);
  --pixilui-accent-foreground-dark: oklch(0.2 0.03 155);
}

[data-accent="amber"] {
  --pixilui-accent-light: oklch(0.8 0.16 78);
  --pixilui-accent-strong-light: oklch(0.55 0.12 65);
  --pixilui-accent-subtle-light: oklch(0.965 0.045 85);
  --pixilui-accent-foreground-light: oklch(0.25 0.05 65);
  --pixilui-accent-dark: oklch(0.82 0.15 80);
  --pixilui-accent-strong-dark: oklch(0.84 0.13 82);
  --pixilui-accent-subtle-dark: oklch(0.3 0.05 78);
  --pixilui-accent-foreground-dark: oklch(0.24 0.05 65);
}

[data-accent="orange"] {
  --pixilui-accent-light: oklch(0.56 0.17 42);
  --pixilui-accent-strong-light: oklch(0.52 0.16 42);
  --pixilui-accent-subtle-light: oklch(0.96 0.03 50);
  --pixilui-accent-foreground-light: oklch(0.99 0.01 50);
  --pixilui-accent-dark: oklch(0.72 0.16 48);
  --pixilui-accent-strong-dark: oklch(0.78 0.13 50);
  --pixilui-accent-subtle-dark: oklch(0.3 0.06 45);
  --pixilui-accent-foreground-dark: oklch(0.2 0.03 45);
}

[data-accent="coral"] {
  --pixilui-accent-light: oklch(0.56 0.17 28);
  --pixilui-accent-strong-light: oklch(0.52 0.16 28);
  --pixilui-accent-subtle-light: oklch(0.96 0.025 28);
  --pixilui-accent-foreground-light: oklch(0.99 0.01 28);
  --pixilui-accent-dark: oklch(0.72 0.15 30);
  --pixilui-accent-strong-dark: oklch(0.78 0.12 30);
  --pixilui-accent-subtle-dark: oklch(0.3 0.06 28);
  --pixilui-accent-foreground-dark: oklch(0.2 0.03 28);
}

[data-accent="rose"] {
  --pixilui-accent-light: oklch(0.55 0.2 8);
  --pixilui-accent-strong-light: oklch(0.51 0.19 8);
  --pixilui-accent-subtle-light: oklch(0.96 0.025 8);
  --pixilui-accent-foreground-light: oklch(0.99 0.01 8);
  --pixilui-accent-dark: oklch(0.56 0.2 8);
  --pixilui-accent-strong-dark: oklch(0.78 0.12 8);
  --pixilui-accent-subtle-dark: oklch(0.29 0.07 8);
  --pixilui-accent-foreground-dark: oklch(0.99 0.01 8);
}

/* ------------------------------------------------------------------ 5. variants and utilities */

/* Hover only where there is a fine pointer (GROUND-RULES section 2). Use hover-fine:, not hover:. */
@custom-variant hover-fine {
  @media (hover: hover) and (pointer: fine) {
    &:hover {
      @slot;
    }
  }
}
@custom-variant group-hover-fine {
  @media (hover: hover) and (pointer: fine) {
    &:is(:where(.group):hover *) {
      @slot;
    }
  }
}

/* The focus ring every interactive item uses: focus-visible:focus-ring */
@utility focus-ring {
  outline: var(--pixilui-focus-outline-width) solid var(--pixilui-focus-outline, var(--pixilui-accent, color-mix(in oklch, var(--pixilui-accent-dark) calc(var(--pixilui-dark) * 100%), var(--pixilui-accent-light))));
  outline-offset: var(--pixilui-focus-outline-offset);
}

/* A 1px border drawn as an overlay, so it never changes a measurement. */
@utility hairline {
  position: relative;
  &::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    border: 1px solid var(--pixilui-hairline, var(--pixilui-border));
    pointer-events: none;
  }
}
@utility hairline-subtle {
  --pixilui-hairline: var(--pixilui-border-subtle);
}
@utility hairline-strong {
  --pixilui-hairline: var(--pixilui-border-strong);
}
@utility hairline-accent {
  --pixilui-hairline: var(--pixilui-accent, color-mix(in oklch, var(--pixilui-accent-dark) calc(var(--pixilui-dark) * 100%), var(--pixilui-accent-light)));
}
@utility hairline-danger {
  --pixilui-hairline: var(--pixilui-danger);
}

/* Tabular numbers in the mono face: timestamps, IDs, counts. */
@utility numeric {
  font-family: var(--pixilui-font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}

/* ------------------------------------------------------------------ 6. Tailwind mapping */
@theme inline {
  /* colour: shared names (same meaning as shadcn's, so an app's own theme may set them) */
  --color-background: var(--pixilui-background);
  --color-foreground: var(--pixilui-foreground);
  --color-border: var(--pixilui-border);

  /* colour: pixilui roles */
  --color-surface: var(--pixilui-surface);
  --color-surface-raised: var(--pixilui-surface-raised);
  --color-surface-muted: var(--pixilui-surface-muted);
  --color-surface-inverted: var(--pixilui-surface-inverted);
  --color-field: var(--pixilui-field);
  --color-border-subtle: var(--pixilui-border-subtle);
  --color-border-strong: var(--pixilui-border-strong);
  /* accents resolve where they are used: the nearest data-accent palette, the nearest theme */
  --color-accent-solid: var(--pixilui-accent, color-mix(in oklch, var(--pixilui-accent-dark) calc(var(--pixilui-dark) * 100%), var(--pixilui-accent-light)));
  --color-accent-strong: var(--pixilui-accent-strong, color-mix(in oklch, var(--pixilui-accent-strong-dark) calc(var(--pixilui-dark) * 100%), var(--pixilui-accent-strong-light)));
  --color-accent-subtle: var(--pixilui-accent-subtle, color-mix(in oklch, var(--pixilui-accent-subtle-dark) calc(var(--pixilui-dark) * 100%), var(--pixilui-accent-subtle-light)));
  --color-accent-contrast: var(--pixilui-accent-foreground, color-mix(in oklch, var(--pixilui-accent-foreground-dark) calc(var(--pixilui-dark) * 100%), var(--pixilui-accent-foreground-light)));
  --color-success: var(--pixilui-success);
  --color-success-subtle: var(--pixilui-success-subtle);
  --color-success-foreground: var(--pixilui-success-foreground);
  --color-warning: var(--pixilui-warning);
  --color-warning-subtle: var(--pixilui-warning-subtle);
  --color-warning-foreground: var(--pixilui-warning-foreground);
  --color-danger: var(--pixilui-danger);
  --color-danger-subtle: var(--pixilui-danger-subtle);
  --color-danger-foreground: var(--pixilui-danger-foreground);
  --color-info: var(--pixilui-info);
  --color-info-subtle: var(--pixilui-info-subtle);
  --color-info-foreground: var(--pixilui-info-foreground);
  --color-control-on: var(--pixilui-control-on, var(--pixilui-accent, color-mix(in oklch, var(--pixilui-accent-dark) calc(var(--pixilui-dark) * 100%), var(--pixilui-accent-light))));
  --color-control-track: var(--pixilui-control-track);
  --color-control-thumb: var(--pixilui-control-thumb);

  /* colour: text-only roles (text-secondary, text-muted, text-inverted) */
  --text-color-secondary: var(--pixilui-text-secondary);
  --text-color-muted: var(--pixilui-text-muted);
  --text-color-inverted: var(--pixilui-text-inverted);

  /* focus */
  --outline-color-focus: var(--pixilui-focus-outline, var(--pixilui-accent, color-mix(in oklch, var(--pixilui-accent-dark) calc(var(--pixilui-dark) * 100%), var(--pixilui-accent-light))));

  /* shape */
  --radius-control: var(--pixilui-radius-control);
  --radius-panel: var(--pixilui-radius-panel);
  --radius-surface: var(--pixilui-radius-surface);
  --radius-pill: var(--pixilui-radius-pill);
  --spacing-control-sm: var(--pixilui-control-height-sm);
  --spacing-control-md: var(--pixilui-control-height-md);
  --spacing-control-lg: var(--pixilui-control-height-lg);

  /* type */
  --font-display: var(--pixilui-font-display);
  --font-body: var(--pixilui-font-body);
  --font-mono: var(--pixilui-font-mono);
  --text-12: var(--pixilui-text-12);
  --text-12--line-height: var(--pixilui-leading-body);
  --text-12--letter-spacing: var(--pixilui-tracking-body);
  --text-14: var(--pixilui-text-14);
  --text-14--line-height: var(--pixilui-leading-body);
  --text-14--letter-spacing: var(--pixilui-tracking-body);
  --text-16: var(--pixilui-text-16);
  --text-16--line-height: var(--pixilui-leading-body);
  --text-16--letter-spacing: var(--pixilui-tracking-body);
  --text-18: var(--pixilui-text-18);
  --text-18--line-height: var(--pixilui-leading-body);
  --text-18--letter-spacing: var(--pixilui-tracking-body);
  --text-22: var(--pixilui-text-22);
  --text-22--line-height: var(--pixilui-leading-body);
  --text-22--letter-spacing: var(--pixilui-tracking-body);
  --text-28: var(--pixilui-text-28);
  --text-28--line-height: var(--pixilui-leading-body);
  --text-28--letter-spacing: var(--pixilui-tracking-body);
  --text-36: var(--pixilui-text-36);
  --text-36--line-height: var(--pixilui-leading-display);
  --text-36--letter-spacing: var(--pixilui-tracking-display);
  --text-52: var(--pixilui-text-52);
  --text-52--line-height: var(--pixilui-leading-display);
  --text-52--letter-spacing: var(--pixilui-tracking-display);
  --text-72: var(--pixilui-text-72);
  --text-72--line-height: var(--pixilui-leading-display);
  --text-72--letter-spacing: var(--pixilui-tracking-display);

  /* elevation */
  --shadow-resting: var(--pixilui-shadow-resting);
  --shadow-raised: var(--pixilui-shadow-raised);
  --shadow-floating: var(--pixilui-shadow-floating);
  --shadow-control: var(--pixilui-shadow-control);
  --inset-shadow-highlight: var(--pixilui-highlight);

  /* motion: duration-fast, ease-enter, blur-subtle ... */
  --transition-duration-instant: var(--pixilui-duration-instant);
  --transition-duration-quick: var(--pixilui-duration-quick);
  --transition-duration-fast: var(--pixilui-duration-fast);
  --transition-duration-standard: var(--pixilui-duration-standard);
  --transition-duration-medium: var(--pixilui-duration-medium);
  --transition-duration-slow: var(--pixilui-duration-slow);
  --transition-duration-considered: var(--pixilui-duration-considered);
  --transition-duration-very-slow: var(--pixilui-duration-very-slow);
  --transition-duration-spring: var(--pixilui-duration-spring);
  --ease-enter: var(--pixilui-ease-enter);
  --ease-smooth-out: var(--pixilui-ease-smooth-out);
  --ease-exit: var(--pixilui-ease-exit);
  --ease-move: var(--pixilui-ease-move);
  --ease-swap: var(--pixilui-ease-swap);
  --ease-bounce: var(--pixilui-ease-bounce);
  --ease-spring: var(--pixilui-ease-spring);
  --blur-subtle: var(--pixilui-blur-subtle);
  --blur-soft: var(--pixilui-blur-soft);
  --blur-text: var(--pixilui-blur-text);
}

Usage#

Use it when

  • Installed once per app, automatically, by the first pixilui item you add.

Use something else when

  • Do not copy values out of it into components; read the utilities (bg-surface, rounded-control, duration-fast).

Accessibility#

  • Text roles clear 4.5:1 on their surfaces in light and dark.
  • Accent fills keep light text at 4.5:1 or switch to dark text (amber).
  • focus-ring draws a 2px outline in the accent with a 2px offset.

Motion#

  • Springs: snappy for presses and toggles, smooth for panels and anything shrinking, morph for anything growing.
  • Every item reads motionTokens and has a reduced-motion branch: a short fade or an instant change.

Responsive#

  • Tokens are size-independent; items size with their container.

Performance#

  • One stylesheet of custom properties; utilities compile through @theme inline, so no runtime theme provider.

Notes for AI#

Also in the markdown page and llms.txt
  1. Use semantic utilities only: bg-surface, bg-surface-muted, text-foreground, text-secondary, text-muted, border-border, hairline, rounded-control, rounded-panel, h-control-md, text-14, shadow-raised.
  2. The accent fill is bg-accent-solid with text-accent-contrast. bg-accent stays shadcn's hover grey.
  3. Hover styles use hover-fine:, focus uses focus-visible:focus-ring.
  4. Theme with data-theme="dark" (or .dark) and data-accent="violet|blue|green|amber|orange|coral|rose|neutral".