Theming
Tokens
Section titled “Tokens”Every component reads CSS custom properties. Override them anywhere after the stylesheet and the whole set follows.
:root { --primary: oklch(0.55 0.2 264); --primary-foreground: oklch(0.98 0 0); --radius: 0.5rem;}| Token | Used for |
|---|---|
--background / --foreground | Page surface and its text |
--card / --card-foreground | Card surface |
--popover / --popover-foreground | Dropdown menu surface |
--primary / --primary-foreground | Primary button, focus accents |
--secondary / --secondary-foreground | Secondary button |
--muted / --muted-foreground | Descriptions, placeholders |
--accent / --accent-foreground | Hover and focus states |
--destructive | Danger surfaces and text — the surface is a wash of it, the text is it |
--border / --input / --ring | Borders, field borders, focus ring |
--radius | Base corner radius; sm/md/lg/xl derive from it |
Because the names match shadcn/ui, a palette generated for shadcn drops in unchanged.
Dark mode
Section titled “Dark mode”Dark tokens live under the .dark class on <html>.
<html class="dark">partialkit ships a toggle that persists the choice under pk-theme in localStorage:
<button class="btn btn-outline btn-icon" data-pk-theme aria-label="Toggle theme">☾</button>| Value | Behaviour |
|---|---|
data-pk-theme | Flips between light and dark |
data-pk-theme="dark" | Forces dark |
data-pk-theme="light" | Forces light |
data-pk-theme="system" | Clears the choice and follows the OS |
Add this to <head> so the page does not flash before the runtime loads:
<script> if (localStorage.getItem("pk-theme") === "dark" || (!localStorage.getItem("pk-theme") && matchMedia("(prefers-color-scheme: dark)").matches)) { document.documentElement.classList.add("dark"); }</script>A pk:theme:change event fires on <html> with { theme, dark } whenever it changes.
Contrast is tested, not assumed
Section titled “Contrast is tested, not assumed”axe-core runs against every documentation page in both modes on each build, so a token change that breaks WCAG AA fails CI.
Two exceptions are recorded rather than fixed, both places where shadcn/ui’s own values land under the bar:
| Pairing | Measures | Would need |
|---|---|---|
--destructive text on a wash of itself | 3.98:1 light, 4.28:1 dark | --destructive at oklch(0.50 …), which clears it at 4.95:1 |
--muted-foreground on a muted surface | 4.27:1 to 4.34:1 | a darker muted foreground |
The second covers every place shadcn/ui pairs muted text with a muted surface: a Kbd, an avatar fallback, an avatar group count, and a description inside a checked choice card. All of them pass in dark mode.
Both ship shadcn/ui’s values rather than a fork. The exceptions are narrow — each names the rule and the markup it covers, and the second is bounded to a near miss, so muted text that is genuinely unreadable still fails the build. A test asserts the exceptions are still triggered, so they disappear the day shadcn/ui lifts the colours instead of quietly outliving their reason.
If your product needs AA on destructive text, one line gets you there:
:root { --destructive: oklch(0.50 0.245 27.325);}<button class="btn btn-destructive">Delete</button><span class="badge badge-destructive">Danger</span>