Theming
A guide to styling components with our color system.
Overview#
Our styling system builds upon shadcn/ui's CSS variables approach with additional status colors and design tokens.
Installation#
Copy and paste the following into your globals.css file:
:root { --radius: 0.75rem; --neutral-hue: 270; --neutral-chroma: 0.004; --neutral-chroma-low: 0.002; /* Background colors, the page is surface-1, cards and popovers surface-3. */ --background: var(--surface-1); --foreground: oklch(0.18 var(--neutral-chroma) var(--neutral-hue)); --card: var(--surface-3); --card-foreground: var(--foreground); --popover: var(--surface-3); --popover-foreground: var(--foreground); /* Primary & accent colors */ --primary: oklch(0.6 0.2 250); --primary-foreground: oklch(1 0 0); --primary-soft-foreground: var(--primary); --neutral: oklch(0.21 var(--neutral-chroma) var(--neutral-hue)); --neutral-foreground: oklch( 0.98 var(--neutral-chroma-low) var(--neutral-hue) ); --secondary: oklch(0.92 0 0); --secondary-foreground: oklch(0.32 var(--neutral-chroma) var(--neutral-hue)); --muted: oklch(0.94 0 0); --muted-foreground: oklch(0.5 var(--neutral-chroma) var(--neutral-hue)); --accent: oklch(0.91 0 0); --accent-foreground: oklch(0.22 var(--neutral-chroma) var(--neutral-hue)); --destructive: oklch(0.53 0.19 25); --destructive-foreground: oklch( 0.98 var(--neutral-chroma-low) var(--neutral-hue) ); /* Same idea for `destructive-soft`'s label on its tinted plate. */ --destructive-soft-foreground: var(--destructive); --chrome: var(--neutral); --chrome-foreground: var(--neutral-foreground); --chrome-muted-foreground: color-mix( in oklab, var(--chrome-foreground) 71%, var(--chrome) ); --chrome-hover: color-mix( in oklab, var(--chrome-foreground) 10%, transparent ); --chrome-border: color-mix( in oklab, var(--chrome-foreground) 12%, transparent ); --primary-hover: color-mix(in oklch, var(--primary), black 5%); --neutral-hover: color-mix(in oklch, var(--neutral), white 10%); --destructive-hover: color-mix(in oklch, var(--destructive), black 5%); --secondary-hover: color-mix(in oklch, var(--secondary), black 5%); --outline-hover: color-mix(in oklch, var(--card), black 5%); /* Pressed, one step past hover in the same direction. */ --primary-active: color-mix(in oklch, var(--primary), black 8%); --neutral-active: color-mix(in oklch, var(--neutral), white 14%); --destructive-active: color-mix(in oklch, var(--destructive), black 8%); --secondary-active: color-mix(in oklch, var(--secondary), black 8%); --outline-active: color-mix(in oklch, var(--card), black 8%); /* Borders & inputs */ --border: color-mix(in oklab, var(--foreground) 10%, transparent); --input: var(--surface-3); --input-elevated: oklch(0 0 0 / 8%); --ring: oklch(0.55 0.2 250); --scrollbar: oklch(0.87 0 0); --sidebar: var(--surface-1); --sidebar-foreground: var(--foreground); --sidebar-primary: var(--primary); --sidebar-primary-foreground: var(--primary-foreground); --sidebar-accent: var(--accent); --sidebar-accent-foreground: var(--accent-foreground); /* Opaque --border, for the resize rail where a translucent line is too faint. */ --sidebar-border: oklch(0.9 var(--neutral-chroma) var(--neutral-hue)); --sidebar-ring: var(--ring); /* Status colors */ --danger: oklch(0.97 0.04 25); --danger-foreground: oklch(0.55 0.18 25); --danger-border: oklch(0.9 0.1 25); --warning: oklch(0.98 0.06 85); --warning-foreground: oklch(0.58 0.14 85); --warning-border: oklch(0.92 0.1 85); --info: oklch(0.97 0.04 250); --info-foreground: oklch(0.45 0.2 250); --info-border: oklch(0.9 0.1 250); --success: oklch(0.97 0.04 145); --success-foreground: oklch(0.48 0.18 145); --success-border: oklch(0.9 0.1 145); --surface-1: oklch(0.97 0 0); --surface-2: oklch(0.985 0 0); --surface-3: oklch(1 0 0); --surface-4: oklch(1 0 0); --surface-5: oklch(1 0 0); --surface-6: oklch(1 0 0); --surface-7: oklch(1 0 0); --surface-8: oklch(1 0 0); --surface-hover: oklch(0 0 0 / 0.06); --surface-active: oklch(0 0 0 / 0.08); --surface-selected: oklch(0 0 0 / 0.1); --surface-rim-color: oklch(0 0 0 / 0); --surface-shadow-ring: oklch(0 0 0 / 0.06); /* crisp edge */ --surface-shadow-near: oklch(0 0 0 / 0.06); /* 1px blur (contact) */ --surface-shadow-mid: oklch(0 0 0 / 0.05); /* 3–6px blur */ --surface-shadow-far: oklch(0 0 0 / 0.04); /* 12–24px blur */ --surface-shadow-ambient: oklch(0 0 0 / 0.03); /* 48–96px blur */ --surface-shadow-1: 0 0 0 1px var(--surface-shadow-ring); --surface-shadow-2: 0 0 0 1px var(--surface-shadow-ring), 0 1px 1px -0.5px var(--surface-shadow-near); --surface-shadow-3: 0 0 0 1px var(--surface-shadow-ring), 0 1px 1px -0.5px var(--surface-shadow-near), 0 3px 3px -1.5px var(--surface-shadow-mid); --surface-shadow-4: 0 0 0 1px var(--surface-shadow-ring), 0 1px 1px -0.5px var(--surface-shadow-near), 0 3px 3px -1.5px var(--surface-shadow-mid), 0 6px 6px -3px var(--surface-shadow-mid); --surface-shadow-5: 0 0 0 1px var(--surface-shadow-ring), 0 1px 1px -0.5px var(--surface-shadow-near), 0 3px 3px -1.5px var(--surface-shadow-mid), 0 6px 6px -3px var(--surface-shadow-mid), 0 12px 12px -6px var(--surface-shadow-far); --surface-shadow-6: 0 0 0 1px var(--surface-shadow-ring), 0 1px 1px -0.5px var(--surface-shadow-near), 0 3px 3px -1.5px var(--surface-shadow-mid), 0 6px 6px -3px var(--surface-shadow-mid), 0 12px 12px -6px var(--surface-shadow-far), 0 24px 24px -12px var(--surface-shadow-far); --surface-shadow-7: 0 0 0 1px var(--surface-shadow-ring), 0 1px 1px -0.5px var(--surface-shadow-near), 0 3px 3px -1.5px var(--surface-shadow-mid), 0 6px 6px -3px var(--surface-shadow-mid), 0 12px 12px -6px var(--surface-shadow-far), 0 24px 24px -12px var(--surface-shadow-far), 0 48px 48px -24px var(--surface-shadow-ambient); --surface-shadow-8: 0 0 0 1px var(--surface-shadow-ring), 0 1px 1px -0.5px var(--surface-shadow-near), 0 3px 3px -1.5px var(--surface-shadow-mid), 0 6px 6px -3px var(--surface-shadow-mid), 0 12px 12px -6px var(--surface-shadow-far), 0 24px 24px -12px var(--surface-shadow-far), 0 48px 48px -24px var(--surface-shadow-ambient), 0 96px 96px -48px var(--surface-shadow-ambient); --surface-rim-1: 0 0 transparent; --surface-rim-2: 0 0 transparent; --surface-rim-3: 0 0 transparent; --surface-rim-4: 0 0 transparent; --surface-rim-5: 0 0 transparent; --surface-rim-6: 0 0 transparent; --surface-rim-7: 0 0 transparent; --surface-rim-8: 0 0 transparent; --surface-shadow-combined-1: var(--surface-shadow-1), var(--surface-rim-1); --surface-shadow-combined-2: var(--surface-shadow-2), var(--surface-rim-2); --surface-shadow-combined-3: var(--surface-shadow-3), var(--surface-rim-3); --surface-shadow-combined-4: var(--surface-shadow-4), var(--surface-rim-4); --surface-shadow-combined-5: var(--surface-shadow-5), var(--surface-rim-5); --surface-shadow-combined-6: var(--surface-shadow-6), var(--surface-rim-6); --surface-shadow-combined-7: var(--surface-shadow-7), var(--surface-rim-7); --surface-shadow-combined-8: var(--surface-shadow-8), var(--surface-rim-8); --chrome-shadow: var(--surface-shadow-1); } .dark { /* Dark-mode color overrides */ --foreground: oklch(0.94 var(--neutral-chroma) var(--neutral-hue)); --neutral: oklch(0.95 var(--neutral-chroma) var(--neutral-hue)); --neutral-foreground: oklch(0.13 var(--neutral-chroma) var(--neutral-hue)); --secondary: oklch(0.35 var(--neutral-chroma) var(--neutral-hue)); --secondary-foreground: oklch(0.94 var(--neutral-chroma) var(--neutral-hue)); --muted: oklch(0.24 var(--neutral-chroma) var(--neutral-hue)); --muted-foreground: oklch(0.73 var(--neutral-chroma) var(--neutral-hue)); --accent: oklch(0.31 var(--neutral-chroma) var(--neutral-hue)); --accent-foreground: oklch(0.95 var(--neutral-chroma) var(--neutral-hue)); --destructive: oklch(0.5 0.22 25); --destructive-foreground: oklch(1 0 0); /* Brightened so primary-soft's label stays readable on the darker plate. */ --primary-soft-foreground: color-mix(in oklch, var(--primary), white 30%); /* Same treatment for destructive-soft's label on its tinted plate. */ --destructive-soft-foreground: color-mix( in oklch, var(--destructive), white 30% ); --chrome: oklch(0.159 var(--neutral-chroma) var(--neutral-hue)); --chrome-foreground: var(--foreground); --chrome-muted-foreground: color-mix( in oklab, var(--chrome-foreground) 71%, var(--chrome) ); --chrome-hover: color-mix( in oklab, var(--chrome-foreground) 10%, transparent ); --chrome-border: color-mix( in oklab, var(--chrome-foreground) 12%, transparent ); /* Dark hover, mix toward white so hovered buttons brighten. */ --primary-hover: color-mix(in oklch, var(--primary), white 10%); --neutral-hover: color-mix(in oklch, var(--neutral), black 10%); --destructive-hover: color-mix(in oklch, var(--destructive), white 15%); --secondary-hover: color-mix(in oklch, var(--secondary), white 5%); --outline-hover: color-mix(in oklch, var(--card), white 5%); --primary-active: color-mix(in oklch, var(--primary), white 14%); --neutral-active: color-mix(in oklch, var(--neutral), black 14%); --destructive-active: color-mix(in oklch, var(--destructive), black 8%); --secondary-active: color-mix(in oklch, var(--secondary), white 8%); --outline-active: color-mix(in oklch, var(--card), white 8%); --border: color-mix(in oklab, var(--foreground) 10%, transparent); --input-elevated: oklch(1 0 0 / 9%); --scrollbar: oklch(0.7 var(--neutral-chroma) var(--neutral-hue)); /* Only --sidebar-border needs a dark value; the rest are aliases. */ --sidebar-border: oklch(0.28 var(--neutral-chroma) var(--neutral-hue)); /* Status */ --danger: oklch(0.26 0.07 25); --danger-foreground: oklch(0.75 0.15 25); --danger-border: oklch(0.36 0.11 25); --warning: oklch(0.28 0.06 85); --warning-foreground: oklch(0.78 0.12 85); --warning-border: oklch(0.38 0.09 85); --info: oklch(0.26 0.07 250); --info-foreground: oklch(0.72 0.16 250); --info-border: oklch(0.36 0.11 250); --success: oklch(0.26 0.07 145); --success-foreground: oklch(0.72 0.15 145); --success-border: oklch(0.36 0.11 145); /* Surface ladder, progressive lightness with brand-hue tint */ --surface-1: oklch(0.205 var(--neutral-chroma) var(--neutral-hue)); --surface-2: oklch(0.235 var(--neutral-chroma) var(--neutral-hue)); --surface-3: oklch(0.264 var(--neutral-chroma) var(--neutral-hue)); --surface-4: oklch(0.293 var(--neutral-chroma) var(--neutral-hue)); --surface-5: oklch(0.321 var(--neutral-chroma) var(--neutral-hue)); --surface-6: oklch(0.348 var(--neutral-chroma) var(--neutral-hue)); --surface-7: oklch(0.375 var(--neutral-chroma) var(--neutral-hue)); --surface-8: oklch(0.402 var(--neutral-chroma) var(--neutral-hue)); /* State overlays flip to white-on-black */ --surface-hover: oklch(1 0 0 / 0.06); --surface-active: oklch(1 0 0 / 0.08); --surface-selected: oklch(1 0 0 / 0.1); --surface-rim-color: oklch(1 0 0 / 0.04); /* Dark-only tokens used by the rim recipe */ --surface-hi-base: oklch(1 0 0 / 0.01); --surface-hi-mid: oklch(1 0 0 / 0.02); --surface-hi-high: oklch(1 0 0 / 0.04); --surface-hi-peak: oklch(1 0 0 / 0.06); --surface-ring-base: oklch(1 0 0 / 0.02); --surface-ring-mid: oklch(1 0 0 / 0.04); --surface-ring-high: oklch(1 0 0 / 0.06); --surface-shadow-near: oklch(0 0 0 / 0.18); --surface-shadow-mid: oklch(0 0 0 / 0.16); --surface-shadow-far: oklch(0 0 0 / 0.14); --surface-shadow-ambient: oklch(0 0 0 / 0.12); /* Drop-shadow + outer-ring portion of the dark recipe (no insets here) */ --surface-shadow-1: 0 0 transparent; --surface-shadow-2: 0 1px 1px -0.5px var(--surface-shadow-near); --surface-shadow-3: 0 0 0 1px oklch(0 0 0 / 0.12), 0 1px 1px -0.5px var(--surface-shadow-near), 0 3px 3px -1.5px var(--surface-shadow-mid); --surface-shadow-4: 0 0 0 1px oklch(0 0 0 / 0.14), 0 1px 1px -0.5px var(--surface-shadow-near), 0 3px 3px -1.5px var(--surface-shadow-mid), 0 6px 6px -3px var(--surface-shadow-mid); --surface-shadow-5: 0 0 0 1px oklch(0 0 0 / 0.16), 0 1px 1px -0.5px var(--surface-shadow-near), 0 3px 3px -1.5px var(--surface-shadow-mid), 0 6px 6px -3px var(--surface-shadow-mid), 0 12px 12px -6px var(--surface-shadow-far); --surface-shadow-6: 0 0 0 1px oklch(0 0 0 / 0.18), 0 1px 1px -0.5px var(--surface-shadow-near), 0 3px 3px -1.5px var(--surface-shadow-mid), 0 6px 6px -3px var(--surface-shadow-mid), 0 12px 12px -6px var(--surface-shadow-far), 0 24px 24px -12px var(--surface-shadow-far); --surface-shadow-7: 0 0 0 1px oklch(0 0 0 / 0.2), 0 1px 1px -0.5px var(--surface-shadow-near), 0 3px 3px -1.5px var(--surface-shadow-mid), 0 6px 6px -3px var(--surface-shadow-mid), 0 12px 12px -6px var(--surface-shadow-far), 0 24px 24px -12px var(--surface-shadow-far), 0 48px 48px -24px var(--surface-shadow-ambient); --surface-shadow-8: 0 0 0 1px oklch(0 0 0 / 0.22), 0 1px 1px -0.5px var(--surface-shadow-near), 0 3px 3px -1.5px var(--surface-shadow-mid), 0 6px 6px -3px var(--surface-shadow-mid), 0 12px 12px -6px var(--surface-shadow-far), 0 24px 24px -12px var(--surface-shadow-far), 0 48px 48px -24px var(--surface-shadow-ambient), 0 96px 96px -48px var(--surface-shadow-ambient); --surface-rim-1: inset 0 0 0 1px var(--surface-ring-base); --surface-rim-2: inset 0 1px 0 0 var(--surface-hi-base), inset 0 0 0 1px var(--surface-ring-base); --surface-rim-3: inset 0 1px 0 0 var(--surface-hi-mid), inset 0 0 0 1px var(--surface-ring-base); --surface-rim-4: inset 0 1px 0 0 var(--surface-hi-mid), inset 0 0 0 1px var(--surface-ring-mid); --surface-rim-5: inset 0 1px 0 0 var(--surface-hi-high), inset 0 0 0 1px var(--surface-ring-mid); --surface-rim-6: inset 0 1px 0 0 var(--surface-hi-high), inset 0 0 0 1px var(--surface-ring-high); --surface-rim-7: inset 0 1px 0 0 var(--surface-hi-peak), inset 0 0 0 1px var(--surface-ring-high); --surface-rim-8: inset 0 1px 0 0 var(--surface-hi-peak), inset 0 0 0 1px var(--surface-ring-high); --surface-shadow-combined-1: var(--surface-shadow-1), var(--surface-rim-1); --surface-shadow-combined-2: var(--surface-shadow-2), var(--surface-rim-2); --surface-shadow-combined-3: var(--surface-shadow-3), var(--surface-rim-3); --surface-shadow-combined-4: var(--surface-shadow-4), var(--surface-rim-4); --surface-shadow-combined-5: var(--surface-shadow-5), var(--surface-rim-5); --surface-shadow-combined-6: var(--surface-shadow-6), var(--surface-rim-6); --surface-shadow-combined-7: var(--surface-shadow-7), var(--surface-rim-7); --surface-shadow-combined-8: var(--surface-shadow-8), var(--surface-rim-8); --chrome-shadow: 0 0 0 1px oklch(0 0 0 / 0.7), inset 0 1px 0 0 oklch(1 0 0 / 0.065); } @theme inline { /* Colors */ --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-neutral: var(--neutral); --color-neutral-foreground: var(--neutral-foreground); --color-chrome: var(--chrome); --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-destructive: var(--destructive); --color-destructive-foreground: var(--destructive-foreground); --color-border: var(--border); --color-input: var(--input); --color-input-elevated: var(--input-elevated); --color-ring: var(--ring); --color-scrollbar: var(--scrollbar); /* Sidebar colors */ --color-sidebar: var(--sidebar); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); /* Status colors */ --color-danger: var(--danger); --color-danger-foreground: var(--danger-foreground); --color-danger-border: var(--danger-border); --color-warning: var(--warning); --color-warning-foreground: var(--warning-foreground); --color-warning-border: var(--warning-border); --color-info: var(--info); --color-info-foreground: var(--info-foreground); --color-info-border: var(--info-border); --color-success: var(--success); --color-success-foreground: var(--success-foreground); --color-success-border: var(--success-border); /* Radius */ --radius-xs: calc(var(--radius) - 6px); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 2px); --radius-2xl: calc(var(--radius) + 4px); --radius-3xl: calc(var(--radius) + 6px); --radius-4xl: calc(var(--radius) + 8px); /* Easings */ --ease-in-cubic: cubic-bezier(0.55, 0.055, 0.675, 0.19); --ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1); --ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1); --ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1); /* Inset shadows for bordered elements (applied to before pseudo-element inset by 1px) */ --shadow-inset: 0 1px 2px 0 oklch(0 0 0 / 0.08); --shadow-inset-highlight: 0 -1px oklch(1 0 0 / 0.04); --color-surface-1: var(--surface-1); --color-surface-2: var(--surface-2); --color-surface-3: var(--surface-3); --color-surface-4: var(--surface-4); --color-surface-5: var(--surface-5); --color-surface-6: var(--surface-6); --color-surface-7: var(--surface-7); --color-surface-8: var(--surface-8); /* Generates bg-surface-hover / -active / -selected. */ --color-surface-hover: var(--surface-hover); --color-surface-active: var(--surface-active); --color-surface-selected: var(--surface-selected); } /* Base UI requirements, plus the chrome-surface contract. */ @layer base { body { position: relative; } .root { isolation: isolate; } } [data-surface="chrome"] { --foreground: var(--chrome-foreground); --muted-foreground: var(--chrome-muted-foreground); --border: var(--chrome-border); --muted: var(--chrome-hover); --accent: var(--chrome-hover); --accent-foreground: var(--chrome-foreground); --input: var(--chrome-hover); --surface-hover: var(--chrome-hover); --surface-active: var(--chrome-hover); }
Extended Color Variables#
Cubby UI extends the standard shadcn/ui palette with additional tokens:
--neutral/--neutral-foreground: Neutral button variant--danger/--danger-foreground/--danger-border: Error states with background tint--warning/--warning-foreground/--warning-border: Warning states--info/--info-foreground/--info-border: Informational states--success/--success-foreground/--success-border: Success states--sidebar-*: Dedicated sidebar color tokens--scrollbar: Scrollbar thumb color
Brand-hue tint controls#
Three tokens at the top of :root control the warmth of the entire neutral palette:
--neutral-hue: hue angle (0–360). The default270is a cool violet.--neutral-chroma: chroma for mid-tone neutrals. The default0.004is barely tinted.--neutral-chroma-low: chroma for near-extreme L (very light / very dark). Kept lower because high chroma at the extremes looks garish.
Change these to retune the whole stack. Every neutral surface, foreground, and sidebar color references them. Try --neutral-hue: 220 for a cooler blue tilt or 30 for a warm tan tilt.
Surface elevation system#
Cubby UI ships with an 8-level surface elevation ladder used by Cards, Popovers, Dialogs, Sheets, Drawers, and more. Each level has paired tokens:
--surface-1through--surface-8: Substrate colors (light: pure neutrals; dark: progressive lightness with brand tint)--surface-shadow-1through--surface-shadow-8: Drop-shadow recipes (composed from--surface-shadow-ring/near/mid/far/ambient)--surface-rim-1through--surface-rim-8: Inset highlight + ring (no-op in light, painted in dark)
State overlays raise an underlying surface by a fixed perceptual delta:
--surface-hover: hover state overlay--surface-active: pressed state overlay--surface-selected: selected state overlay--surface-rim-color: single-edge rim color (used by viewport-flush Sheet/Drawer)
For the full elevation system docs, see Surfaces.
Form-field tokens#
Form fields (Input, Textarea, Checkbox, Radio, etc.) use three dedicated tokens:
--input: Default bg, aliased to--surface-3so it matches Card surfaces--input-elevated: Translucent overlay for theelevatedvariant, adapts to any substrate (use inside Cards, Dialogs, popovers)
See the Forms guide for when to use which variant.
Base UI Requirements#
The @layer base section includes styles required for Base UI components to work correctly:
-
body { position: relative }- Fixes backdrop positioning issues on iOS Safari for dialog and popover components. -
.root { isolation: isolate }- Creates a stacking context for Base UI's portal-based components (dialogs, popovers, tooltips). Apply therootclass to your application wrapper element as shown in the Installation guide.
Dark Mode#
For dark mode setup, see the shadcn/ui Dark Mode guide.