Theming

Every pixilui item reads the same tokens, defined once in foundation.css. You theme the library by changing tokens, never by editing items.

How the tokens are organised#

  • Raw tokens are CSS custom properties named --pixilui-* (for example --pixilui-surface, --pixilui-radius-control). The prefix keeps them apart from shadcn's --background and --accent, which mean something else.
  • Utilities map the raw tokens into Tailwind v4 through @theme inline. Items use only the utilities: bg-surface, text-secondary, border-border, rounded-control, h-control-md, text-14, shadow-raised, duration-fast.
Group Utilities
Surfaces bg-background, bg-surface, bg-surface-raised, bg-surface-muted, bg-surface-inverted
Text text-foreground, text-secondary, text-muted, text-inverted
Borders border-border, border-border-subtle, border-border-strong, and hairline (a 1px overlay that never changes a measurement)
Accent bg-accent-solid, text-accent-contrast, text-accent-strong, bg-accent-subtle
Status bg-success, text-danger, bg-warning-subtle, text-info-foreground, and the same for each of success, warning, danger, info
Radii rounded-control (18px), rounded-panel (26px), rounded-surface (34px), rounded-pill
Control heights h-control-sm (36px), h-control-md (44px), h-control-lg (50px), also as w- and size-
Type text-12, text-14, text-16, text-18, text-22, text-28, text-36, text-52, text-72, with line height and tracking included; font-display, font-body, font-mono
Shadows shadow-resting, shadow-raised, shadow-floating
Motion duration-instant to duration-spring, ease-enter, ease-smooth-out, ease-exit, ease-move, ease-swap

Two names differ from the role names on purpose. The accent fill is bg-accent-solid and text on it is text-accent-contrast, because shadcn uses bg-accent and text-accent-foreground for the grey hover in its menus. pixilui leaves those alone, so your existing shadcn components keep their look.

The type scale (text-12 to text-72) is separate from Tailwind's text-sm and text-xl, so installing pixilui never shifts your typography.

Dark mode#

Light is the default. Dark applies under [data-theme="dark"] or the .dark class, on <html> or on any element:

html
<html data-theme="dark">
html
<html class="dark">

The .dark class is what next-themes and shadcn apps already set, so pixilui follows your existing toggle. There is no prefers-color-scheme default: your app decides. Dark values are tuned separately, not inverted: surfaces step up in lightness as they rise, and text roles keep 4.5:1 contrast on their surfaces.

Because the theme is read where it is used, you can nest themes. A dark preview inside a light page, or a light card inside a dark sidebar, renders correctly:

tsx
<div data-theme="dark">
  <ApprovalCard {...props} />
</div>
ApprovedSent renewal reminders to 3,412 customers

Approved

data-theme="light"
ApprovedSent renewal reminders to 3,412 customers

Approved

data-theme="dark"

Accents#

Eight accents ship with the foundation. Set one with data-accent on any element; without it the accent is neutral (near black in light, near white in dark). Pick a row to try it on the button.

Accent Light fill Dark fill
neutral oklch(0.2 0 0) oklch(0.94 0 0)
violet oklch(0.54 0.21 290) oklch(0.56 0.2 290)
blue oklch(0.56 0.225 263) oklch(0.56 0.2 263)
green oklch(0.53 0.13 155) oklch(0.72 0.15 155)
amber oklch(0.8 0.16 78), dark text oklch(0.82 0.15 80), dark text
orange oklch(0.56 0.17 42) oklch(0.72 0.16 48), dark text
coral oklch(0.56 0.17 28) oklch(0.72 0.15 30), dark text
rose oklch(0.55 0.2 8) oklch(0.56 0.2 8)
html
<body data-accent="violet">

Each accent has four roles: the fill (bg-accent-solid, used by primary buttons, selection and switches), text on the fill (text-accent-contrast), accent-coloured text and icons (text-accent-strong), and a tinted background (bg-accent-subtle). Light fills stay dark enough for white text to clear 4.5:1; amber and the lighter dark fills switch to dark text instead.

The accent is also the focus colour: focus-visible:focus-ring draws a 2px outline in the accent with a 2px offset.

Overriding roles#

To change a role everywhere, override its raw token after the foundation import. Set light values on :root and dark values under your dark selector:

css
:root {
  --pixilui-background: oklch(0.985 0.004 90);
  --pixilui-radius-control: 12px;
  --pixilui-radius-panel: 18px;
  --pixilui-radius-surface: 24px;
}

[data-theme="dark"],
.dark {
  --pixilui-background: oklch(0.15 0.004 90);
}

Keep the radii nested (control smaller than panel, panel smaller than surface), so a control inside a panel inside a surface still looks concentric.

To use your brand colour as the accent, set --pixilui-accent and its three partners. These win over data-accent in both themes:

css
:root {
  --pixilui-accent: oklch(0.55 0.2 145);
  --pixilui-accent-strong: oklch(0.48 0.18 145);
  --pixilui-accent-subtle: oklch(0.96 0.03 145);
  --pixilui-accent-foreground: oklch(0.99 0 0);
}

To keep separate light and dark brand values, override the pairs instead (--pixilui-accent-light, --pixilui-accent-dark, and the same for -strong, -subtle and -foreground), and the theme in force picks one.

Restyling one item#

Items expose their state as data attributes (data-status, data-loading, data-risk), and every item takes className, merged last. Target the attributes from your own CSS or with Tailwind's data-* variants instead of editing the item:

tsx
<ApprovalCard className="data-[risk=high]:shadow-floating" {...props} />