Theming
The tokens every component reads, the full palette, and dark mode.
Components style themselves with Tailwind utilities that point at CSS variables: bg-background, text-foreground, bg-brand and so on. Change a variable and every component follows.
Two registry items
base travels with every component. It adds the tokens a stock shadcn theme does not have: brand, success and warning with their soft and strong steps, destructive-soft, destructive-strong, border-strong, chart-6, two easings and the display font stack. It never changes a variable you already have.
theme is optional. It replaces your colours, radius scale and shadows with the snow-and-water palette, in light and dark.
npx shadcn@latest add @miyuki-ui/themeTokens
| Utility | Use |
|---|---|
bg-background, text-foreground | Page and body text |
bg-card | Raised surfaces: cards, inputs, secondary buttons |
bg-popover | Menus, popovers, tooltips and dialogs |
bg-muted, text-muted-foreground | Wells and tracks, secondary text |
bg-secondary | Soft filled controls |
bg-accent | Hover and highlighted rows |
bg-primary, text-primary-foreground | Ink: the main action |
bg-brand, text-brand-foreground | Water: selected, on, in progress |
bg-brand-soft, text-brand-strong | A water tint behind text, water-coloured text and links |
bg-destructive, bg-destructive-soft, text-destructive-strong | Errors and destructive actions |
bg-success, bg-success-soft, text-success-strong | Success |
bg-warning, bg-warning-soft, text-warning-strong | Warnings |
border-border | Hairlines |
border-border-strong, border-input | Control outlines |
ring-ring | Focus rings |
chart-1 to chart-6 | Data and effect hues |
Each -foreground pairs with its solid fill. Text on a -soft tint uses the matching -strong step. Opacity modifiers work on every token, for example bg-brand/15.
Radius
| Utility | Size | Use |
|---|---|---|
rounded-sm | 6px | Chips inside controls, checkboxes |
rounded-md | 8px | Small buttons, menu items |
rounded-lg | 10px | Buttons, inputs, selects |
rounded-xl | 14px | Cards, popovers, menus |
rounded-2xl | 18px | Dialogs, drawers, large cards |
rounded-3xl | 22px | Hero panels |
Every step follows --radius.
Shadows
shadow-xs to shadow-2xl are tinted blue. In the dark theme they add a thin line of frost along the top edge.
Dark mode
The dark theme turns on with the dark class on <html>. Set the class before the page paints, from a small inline script in <head>, so the page does not flash.
You can also switch part of a page: give any element the dark or light class. The tokens and the dark: variant follow the nearest one.
@custom-variant dark (&:where(.dark, .dark *):not(:where(.light, .light *)));The stylesheet
To set the theme up by hand, paste this into your global stylesheet, after @import "tailwindcss";. It holds the same variables the theme item installs.
@custom-variant dark (
&:where(.dark, .dark *):not(
:where(.dark .light, .dark .light *):not(:where(.dark .light .dark, .dark .light .dark *))
)
);
:root,
.light {
color-scheme: light;
--background: oklch(0.984 0.005 236);
--foreground: oklch(0.245 0.035 248);
--card: oklch(1 0 0);
--card-foreground: oklch(0.245 0.035 248);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.245 0.035 248);
--primary: oklch(0.255 0.04 248);
--primary-foreground: oklch(0.985 0.004 236);
--secondary: oklch(0.953 0.01 236);
--secondary-foreground: oklch(0.27 0.04 248);
--muted: oklch(0.962 0.008 236);
--muted-foreground: oklch(0.51 0.03 244);
--accent: oklch(0.944 0.016 232);
--accent-foreground: oklch(0.255 0.04 248);
--brand: oklch(0.55 0.13 240);
--brand-foreground: oklch(0.99 0.004 236);
--brand-soft: oklch(0.94 0.034 232);
--brand-strong: oklch(0.46 0.125 244);
--destructive: oklch(0.565 0.2 18);
--destructive-foreground: oklch(0.99 0.004 20);
--destructive-soft: oklch(0.955 0.028 15);
--destructive-strong: oklch(0.5 0.19 18);
--success: oklch(0.545 0.11 162);
--success-foreground: oklch(0.99 0.004 160);
--success-soft: oklch(0.955 0.035 165);
--success-strong: oklch(0.44 0.1 162);
--warning: oklch(0.76 0.145 78);
--warning-foreground: oklch(0.28 0.06 70);
--warning-soft: oklch(0.965 0.045 88);
--warning-strong: oklch(0.47 0.1 68);
--border: oklch(0.915 0.012 236);
--border-strong: oklch(0.86 0.018 238);
--input: oklch(0.88 0.016 238);
--ring: oklch(0.55 0.13 240 / 0.4);
--chart-1: oklch(0.6 0.13 240);
--chart-2: oklch(0.7 0.105 178);
--chart-3: oklch(0.61 0.14 295);
--chart-4: oklch(0.79 0.14 82);
--chart-5: oklch(0.63 0.19 15);
--chart-6: oklch(0.72 0.12 350);
--radius: 0.625rem;
--miyuki-shadow-2xs: 0 1px 0 oklch(0.42 0.09 250 / 0.05);
--miyuki-shadow-xs: 0 1px 2px oklch(0.42 0.09 250 / 0.07);
--miyuki-shadow-sm: 0 1px 2px oklch(0.42 0.09 250 / 0.07), 0 3px 8px -3px oklch(0.42 0.09 250 / 0.1);
--miyuki-shadow-md: 0 1px 2px oklch(0.42 0.09 250 / 0.06), 0 8px 20px -6px oklch(0.42 0.09 250 / 0.16);
--miyuki-shadow-lg: 0 2px 4px oklch(0.42 0.09 250 / 0.06), 0 18px 40px -12px oklch(0.42 0.09 250 / 0.22);
--miyuki-shadow-xl: 0 4px 8px oklch(0.42 0.09 250 / 0.06), 0 32px 64px -16px oklch(0.42 0.09 250 / 0.28);
--miyuki-shadow-2xl: 0 48px 96px -24px oklch(0.42 0.09 250 / 0.36);
--shiki-foreground: oklch(0.3 0.035 248);
--shiki-background: transparent;
--shiki-token-constant: oklch(0.5 0.17 20);
--shiki-token-string: oklch(0.47 0.1 162);
--shiki-token-comment: oklch(0.62 0.025 244);
--shiki-token-keyword: oklch(0.5 0.15 295);
--shiki-token-parameter: oklch(0.42 0.06 248);
--shiki-token-function: oklch(0.47 0.13 242);
--shiki-token-string-expression: oklch(0.47 0.1 162);
--shiki-token-punctuation: oklch(0.55 0.03 244);
--shiki-token-link: oklch(0.47 0.13 242);
}
.dark {
color-scheme: dark;
--background: oklch(0.175 0.024 248);
--foreground: oklch(0.95 0.008 236);
--card: oklch(0.21 0.027 248);
--card-foreground: oklch(0.95 0.008 236);
--popover: oklch(0.235 0.03 248);
--popover-foreground: oklch(0.95 0.008 236);
--primary: oklch(0.95 0.008 236);
--primary-foreground: oklch(0.2 0.03 248);
--secondary: oklch(0.265 0.03 248);
--secondary-foreground: oklch(0.95 0.008 236);
--muted: oklch(0.245 0.028 248);
--muted-foreground: oklch(0.7 0.025 240);
--accent: oklch(0.285 0.036 245);
--accent-foreground: oklch(0.95 0.008 236);
--brand: oklch(0.74 0.12 230);
--brand-foreground: oklch(0.18 0.03 248);
--brand-soft: oklch(0.3 0.06 240);
--brand-strong: oklch(0.82 0.1 226);
--destructive: oklch(0.66 0.19 18);
--destructive-foreground: oklch(0.17 0.03 20);
--destructive-soft: oklch(0.29 0.07 18);
--destructive-strong: oklch(0.77 0.15 18);
--success: oklch(0.74 0.12 165);
--success-foreground: oklch(0.18 0.03 165);
--success-soft: oklch(0.29 0.05 165);
--success-strong: oklch(0.81 0.12 165);
--warning: oklch(0.82 0.14 84);
--warning-foreground: oklch(0.22 0.05 80);
--warning-soft: oklch(0.31 0.055 84);
--warning-strong: oklch(0.86 0.13 84);
--border: oklch(0.3 0.03 248);
--border-strong: oklch(0.38 0.035 246);
--input: oklch(0.34 0.033 247);
--ring: oklch(0.74 0.12 230 / 0.45);
--chart-1: oklch(0.72 0.12 232);
--chart-2: oklch(0.78 0.11 178);
--chart-3: oklch(0.72 0.13 295);
--chart-4: oklch(0.84 0.13 84);
--chart-5: oklch(0.7 0.17 16);
--chart-6: oklch(0.78 0.11 350);
--miyuki-shadow-2xs: 0 1px 0 oklch(0.05 0.02 250 / 0.3);
--miyuki-shadow-xs: 0 1px 2px oklch(0.05 0.02 250 / 0.4);
--miyuki-shadow-sm:
inset 0 1px 0 oklch(1 0 0 / 0.04), 0 1px 2px oklch(0.05 0.02 250 / 0.4), 0 3px 8px -3px oklch(0.05 0.02 250 / 0.5);
--miyuki-shadow-md:
inset 0 1px 0 oklch(1 0 0 / 0.05), 0 1px 2px oklch(0.05 0.02 250 / 0.4), 0 8px 20px -6px oklch(0.05 0.02 250 / 0.6);
--miyuki-shadow-lg:
inset 0 1px 0 oklch(1 0 0 / 0.06), 0 2px 4px oklch(0.05 0.02 250 / 0.4),
0 18px 40px -12px oklch(0.05 0.02 250 / 0.7);
--miyuki-shadow-xl:
inset 0 1px 0 oklch(1 0 0 / 0.06), 0 4px 8px oklch(0.05 0.02 250 / 0.4),
0 32px 64px -16px oklch(0.05 0.02 250 / 0.75);
--miyuki-shadow-2xl: inset 0 1px 0 oklch(1 0 0 / 0.06), 0 48px 96px -24px oklch(0.05 0.02 250 / 0.8);
--shiki-foreground: oklch(0.88 0.015 236);
--shiki-token-constant: oklch(0.76 0.13 18);
--shiki-token-string: oklch(0.8 0.1 165);
--shiki-token-comment: oklch(0.58 0.03 244);
--shiki-token-keyword: oklch(0.77 0.11 295);
--shiki-token-parameter: oklch(0.82 0.03 240);
--shiki-token-function: oklch(0.79 0.1 228);
--shiki-token-string-expression: oklch(0.8 0.1 165);
--shiki-token-punctuation: oklch(0.65 0.025 244);
--shiki-token-link: oklch(0.79 0.1 228);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-brand: var(--brand);
--color-brand-foreground: var(--brand-foreground);
--color-brand-soft: var(--brand-soft);
--color-brand-strong: var(--brand-strong);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-destructive-soft: var(--destructive-soft);
--color-destructive-strong: var(--destructive-strong);
--color-success: var(--success);
--color-success-foreground: var(--success-foreground);
--color-success-soft: var(--success-soft);
--color-success-strong: var(--success-strong);
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
--color-warning-soft: var(--warning-soft);
--color-warning-strong: var(--warning-strong);
--color-border: var(--border);
--color-border-strong: var(--border-strong);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-chart-6: var(--chart-6);
--font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif;
--font-display: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif;
--font-mono: var(--font-geist-mono), ui-monospace, SFMono-Regular, monospace;
--font-pixel: var(--font-geist-pixel-square), var(--font-geist-mono), ui-monospace, monospace;
--font-pixel-circle: var(--font-geist-pixel-circle), var(--font-geist-mono), ui-monospace, monospace;
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 18px);
--shadow-2xs: var(--miyuki-shadow-2xs);
--shadow-xs: var(--miyuki-shadow-xs);
--shadow-sm: var(--miyuki-shadow-sm);
--shadow-md: var(--miyuki-shadow-md);
--shadow-lg: var(--miyuki-shadow-lg);
--shadow-xl: var(--miyuki-shadow-xl);
--shadow-2xl: var(--miyuki-shadow-2xl);
--ease-settle: cubic-bezier(0.22, 1, 0.36, 1);
--ease-drift: cubic-bezier(0.45, 0.05, 0.25, 1);
}
@layer base {
*,
::before,
::after {
border-color: var(--border);
}
html {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
body {
background: var(--background);
color: var(--foreground);
font-family: var(--font-sans);
font-synthesis-weight: none;
text-rendering: optimizeLegibility;
}
::selection {
background: color-mix(in oklab, var(--brand) 24%, transparent);
}
:focus-visible {
outline-color: var(--brand);
}
}