Foundations
Tokens and themes
The palette, semantic and component CSS variables, light, dark and auto themes, and overrides from a root or one element.
Token layers
Every data-plf-root element declares the full set of public --plf-* variables in three layers; override the highest layer that fits.
| Layer | Example | Role |
|---|---|---|
| Palette | --plf-blue-600 |
Raw colors, the same in every theme |
| Semantic | --plf-color-primary, --plf-space-4 |
Meaning; themes remap colors to palette values |
| Component | --plf-button-bg |
One component’s override, with a semantic fallback; listed in each component guide |
Variables named --_plf-* are internal and are not a public contract.
Themes and cascade
Set data-plf-theme on a root:
data-plf-theme |
Theme |
|---|---|
| No value | Light |
dark |
Dark |
auto |
Follows the device’s setting |
The root sets the matching native color-scheme (light dark for auto), and each semantic color holds a light and a dark value through light-dark(), which takes the one the scheme names. A script that needs a color sets the variable as an element’s color and reads getComputedStyle(element).color, the value in that element’s theme; reading the variable itself gives the light-dark() text.
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div data-plf-root><div class="card"><div class="card-body">Light</div></div></div>
<div data-plf-root data-plf-theme="dark"><div class="card"><div class="card-body">Dark</div></div></div>
<div data-plf-root data-plf-theme="auto"><div class="card"><div class="card-body">System</div></div></div>
</div><div class="plf:grid plf:grid-cols-1 plf:sm:grid-cols-3 plf:gap-4">
<div data-plf-root><div class="plf:card"><div class="plf:card-body">Light</div></div></div>
<div data-plf-root data-plf-theme="dark"><div class="plf:card"><div class="plf:card-body">Dark</div></div></div>
<div data-plf-root data-plf-theme="auto"><div class="plf:card"><div class="plf:card-body">System</div></div></div>
</div>An unrecognized value falls back to light. A root sets the text color and font for its content but does not paint a page background, and the theme is not stored; Theme Switch changes it from a menu or toggles. A nested root restores the default tokens instead of inheriting its outer root’s.
Overrides
Load your stylesheet after the library CSS and target the root you mean; a value set there reaches light, dark and auto roots alike. A brand is primary and on-primary, as the hover and accent text derive from primary; light-dark() gives each a light and a dark value, as Core’s colors have, and every root picks one by its theme.
.my-plugin[data-plf-root] {
--plf-color-primary: light-dark(#15803d, #4ade80);
--plf-color-on-primary: light-dark(#fff, #052e16);
--plf-radius-lg: 0.25rem;
}The class names the theme, such as a client’s: combine it with data-plf-theme on the root, and give a nested root the class too, as a nested root restores the default tokens. A value for one theme only goes in a [data-plf-theme="dark"] rule.
Every color in Semantic colors is overridden the same way, and the derived colors follow it. A color brings the text on it, and a status color its soft fill:
| You set | Set with it | Follows it |
|---|---|---|
--plf-color-primary, the fill of buttons, solid badges and checkboxes and the focus ring |
--plf-color-on-primary, the text on a primary fill |
The hover; primary-text, its text on the page, such as a link, a checkmark or an outline badge; the primary-subtle and primary-border fills and the focus ring |
--plf-color-success, -warning, -danger or -info, the fill of buttons, solid badges and progress bars and the border of an invalid field |
on-{color}, the text on its fill; {color}-text, its text on the page and on the soft fill, such as an alert’s text or an error message; {color}-bg, the soft fill of soft badges and buttons |
The button hover and {color}-border (danger-subtle too) |
primary-text mixes 80% of primary into the text, which suits a primary with a light and a dark shade, as above. One color for both themes, such as #0ea5e9, sets it with a 50% mix, which reads at 4.5:1 or more for Tailwind CSS’s 400 to 950 colors: --plf-color-primary-text: color-mix(in srgb, var(--plf-color-primary) 50%, var(--plf-color-text)). A status color’s text and soft fill can follow the color, mixed into the root’s text and surface, which keeps the text readable on light and dark roots (6.5:1 or more for Tailwind CSS’s 500 and 600 colors):
.my-plugin[data-plf-root] {
--plf-color-success: light-dark(#15803d, #4ade80);
--plf-color-on-success: light-dark(#fff, #052e16);
--plf-color-success-bg: color-mix(in srgb, var(--plf-color-success) 15%, var(--plf-color-surface));
--plf-color-success-text: color-mix(in srgb, var(--plf-color-success) 40%, var(--plf-color-text));
}One element
Set a semantic or component variable on one element, usually inline. Components read their variables through fallbacks rather than assigning them, so root and element overrides both reach them.
Tokens the root computes from others keep the root’s values on an element: the derived colors, focus-color, space-*, font-size-*, radius-* and the control heights. Set --plf-rem, --plf-spacing and --plf-radius-lg on a root, and with primary on one element, set the colors derived from it there too, such as focus-color, primary-hover and primary-text.
<div class="flex flex-wrap items-center gap-2">
<button class="btn btn-primary">Default</button>
<button class="btn btn-primary" style="--plf-button-bg: var(--plf-green-800); --plf-button-border: var(--plf-green-800); --plf-button-hover-bg: var(--plf-green-900)">Custom</button>
</div><div class="plf:flex plf:flex-wrap plf:items-center plf:gap-2">
<button class="plf:btn plf:btn-primary">Default</button>
<button class="plf:btn plf:btn-primary" style="--plf-button-bg: var(--plf-green-800); --plf-button-border: var(--plf-green-800); --plf-button-hover-bg: var(--plf-green-900)">Custom</button>
</div>Check every custom color pair for contrast in its theme and host page; the library’s contrast checks cover only the defaults.
Palette
Each suffix expands to --plf-{suffix} and holds Tailwind CSS v4’s value of that color, in oklch(); white is #fff.
white
neutral-50neutral-400neutral-500neutral-600neutral-900neutral-950
blue-400blue-600
green-100green-300green-500green-800green-900green-950
amber-100amber-300amber-400amber-800amber-900amber-950
red-100red-300red-500red-700red-900red-950
sky-100sky-300sky-800sky-900sky-950
Semantic colors
Each one can be overridden, as Overrides shows. Color utilities name them as daisyUI does:
surface,surface-raisedandsurface-mutedarebase-100,base-200andbase-300.textisbase-content.dangeriserror.- Each
on-{color}is{color}-content.
| Variable | Light | Dark |
|---|---|---|
--plf-color-primary | blue-600 | blue-400 |
--plf-color-on-primary | white | neutral-950 |
--plf-color-surface | white | neutral-950 |
--plf-color-surface-raised | neutral-50 | neutral-900 |
--plf-color-text | neutral-950 | neutral-50 |
--plf-color-text-muted | neutral-600 | neutral-400 |
--plf-color-border | neutral-500 | neutral-500 |
--plf-color-backdrop | rgb | Same |
--plf-color-success, --plf-color-on-success | green-800, white | green-300, green-950 |
--plf-color-success-bg, --plf-color-success-text | green-100, green-900 | green-950, green-300 |
--plf-color-warning, --plf-color-on-warning | amber-800, white | amber-300, amber-950 |
--plf-color-warning-bg, --plf-color-warning-text | amber-100, amber-900 | amber-950, amber-300 |
--plf-color-danger, --plf-color-on-danger | red-700, white | red-300, red-950 |
--plf-color-danger-bg, --plf-color-danger-text | red-100, red-900 | red-950, red-300 |
--plf-color-info, --plf-color-on-info | sky-800, white | sky-300, sky-950 |
--plf-color-info-bg, --plf-color-info-text | sky-100, sky-900 | sky-950, sky-300 |
Derived colors
These are built from the colors above, so theme and root overrides carry through; an element override does not recompute them (One element). The subtle colors mix with transparent, so they take the surface beneath them: on a card in dark mode they come out a step lighter than on the page.
| Variable | Light | Dark | Mix | Use |
|---|---|---|---|---|
--plf-color-primary-hover | 90% primary, 10% text |
Hover fills: darker in light, lighter in dark | ||
--plf-color-primary-text | 80% primary, 20% text |
Primary as text: links, checkmarks, a current page, a pressed toggle, and soft and outline badges | ||
--plf-color-border-subtle | 30% border |
Decorative borders and dividers | ||
--plf-color-input | 88% border; the full border in dark mode |
Editable field boundaries, kept at 3:1 contrast with the field and the surface | ||
--plf-color-surface-muted | 20% border |
Neutral fills such as skeletons, item hover and hovered input group addon buttons | ||
--plf-color-primary-subtle | 10% primary |
Soft selected or current fills | ||
--plf-color-danger-subtle | 10% danger |
Destructive item hover | ||
--plf-color-primary-border, --plf-color-success-border, --plf-color-warning-border, --plf-color-danger-border, --plf-color-info-border | 40% of the accent | Colored decorative outlines, such as outline Badges | ||
--plf-color-surface-elevated | surface; surface-raised in dark mode |
Cards and floating surfaces: popovers, menus, lists, Command, Hover Card and toasts, a step above the page in dark mode, as on shadcn/ui | ||
--plf-color-surface-hover | surface-raised; surface-muted in dark mode |
Hover and soft fills that also show on a card: outline and ghost Buttons, Choice Cards, muted Items and striped rows | ||
--plf-color-field | surface; 4.5% text in dark mode |
Fields, outline Buttons and the selected tabs-box tab, as shadcn/ui’s dark fill |
||
--plf-color-input-hover | border; 80% border, 20% text in dark mode |
Hovered field boundaries, stronger than input in both themes |
Typography
Each suffix expands to --plf-{suffix}. The Typography guide covers prose, including the --plf-prose-mark-bg and --plf-prose-mark-color highlight overrides.
| Suffix | Default |
|---|---|
font-family |
system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif |
font-family-mono |
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace |
font-size-xs, font-size-sm, font-size-body, font-size-lg, font-size-xl, font-size-2xl, font-size-3xl |
0.75rem, 0.875rem, 1rem, 1.125rem, 1.25rem, 1.5rem, 1.875rem (12–30px at a 16px root) |
font-size-4xl to font-size-9xl |
2.25rem, 3rem, 3.75rem, 4.5rem, 6rem, 8rem |
font-weight-normal, font-weight-medium, font-weight-semibold, font-weight-bold |
400, 500, 600, 700 |
line-height, line-height-tight, line-height-relaxed |
1.5, 1.25, 1.625 |
| Suffix | Default |
|---|---|
rem |
1rem; every size below is a multiple of it, so 1. restores them on a host whose root font size is 62.5% |
spacing |
0.25rem; spacing utilities multiply it by their step, as Tailwind CSS’s --spacing |
space-0 to space-12 |
spacing times 0 to 12, for component CSS |
radius-xs, radius-sm |
0.125rem, 0.25rem |
radius-md |
0.75 × radius-lg, 0.375rem (6px); inner items such as menu items, tabs, chips and keys |
radius-lg |
0.5rem (8px); controls and overlays; radius-md and radius-xl follow it |
radius-xl |
1.5 × radius-lg, 0.75rem (12px); cards and dialogs |
radius-2xl, radius-3xl, radius-4xl |
1rem, 1.5rem, 2rem |
radius-full |
calc; pills |
border-width |
1px |
Sizes are Tailwind CSS’s rem values, as multiples of --plf-rem, so they follow the reader’s browser font size. On a host page whose root font size is not 100%, such as html { font-size: 62.5% }, set --plf-rem on the root to 1rem divided by that factor (Installation).
| Suffix | Default |
|---|---|
control-font-size, control-font-weight |
font-size-sm (14px), font-weight-medium (500) |
input-font-weight |
font-weight-normal (400), for text typed into fields |
control-icon-size |
calc, 16px at a 14px control font |
control-height-sm, control-height-md, control-height-lg |
8, 9 and 10 × spacing: 2rem, 2.25rem, 2.5rem minimum heights |
| Suffix | Light | Dark |
|---|---|---|
shadow-2xs to shadow-2xl |
Tailwind CSS’s shadows: xs for controls and cards, md for popovers, lg for menus, dialogs, sheets and toasts |
The same geometry with three times the alpha (0.15, 0.3; 0.5 for 2xl) |
focus-color, focus-width, focus-offset |
color-primary, 2px, 2px |
Same |
Every control shows one focus outline from the focus variables; fields use an offset of focus-offset − focus-width (0px) so the ring meets their border.
Motion and layers
Durations become 0ms under prefers-reduced-motion: reduce.
| Suffix | Default |
|---|---|
duration-fast, duration-normal, easing |
120ms, 180ms, ease-out |
duration-enter, easing-enter |
200ms, cubic-bezier, for opening dialogs, sheets and toasts; closing is immediate |
z-index-base, z-index-sticky, z-index-overlay, z-index-modal |
0, 900, 1000, 1100; native modal dialogs use the browser top layer instead |
| Suffix | Default |
|---|---|
sticky-offset |
0px; the sticky header’s block size, see Sticky and skip links |
scroll-offset |
sticky-offset plus 1rem, where a link target stops; set sticky-offset on the root |