Colors
Semantic color tokens used throughout the design system.
Color defines the visual tone of a design system. Ink UI uses a palette built around warm gray and orange, expanded into full scales in the OKLCH color space for consistent luminance and accessibility across light and dark modes.
The gray carries a subtle warm bias — warmer than neutral, but less pronounced than Tailwind's Stone — providing a balanced foundation. Orange serves as the primary accent, adding energy and warmth that complements the overall palette.
Gray
A warm gray scale for text, backgrounds, borders, and subtle fills across light and dark modes.
Red
A red scale for destructive actions, errors, and warning states.
Orange
The primary accent color for focus rings, active states, and blockquote accents.
Semantic tokens
These CSS variables map to the scales above and adapt between light and dark modes via the data-theme attribute.
Backgrounds
Actions
States
Borders & focus
Common Tailwind utilities like text-gray-500, bg-gray-100, border-gray-200, and dark: variants map to the underlying Ink UI theme tokens.
Interactive states
Every surface token ships a -hover and -active companion for interactive elements. Rather than fading a base color with opacity — which composites unpredictably against whatever sits behind it and muddies stacked surfaces — each state is a fixed, opaque step derived from the base token with color-mix. Each opaque surface is mixed toward --foreground, so the same formula darkens it in light mode and brightens it in dark mode automatically (solid fills like primary mix toward their own foreground instead). The result is always a real, opaque change that reads regardless of background.
Use them directly as Tailwind utilities — hover:bg-primary-hover, data-popup-open:bg-secondary-active, and so on — so the light/dark logic lives in the theme instead of being restated in every component.
secondary (and the ghost / outline surfaces built on it) stays alpha-based on purpose: it's an overlay tint meant to composite over varying backgrounds, so its -hover and -active steps deepen the alpha rather than swapping to a solid color.
Installation
Copy the CSS code into your global stylesheet, wrapped in a selector that applies the appropriate color scheme based on the data-theme attribute.
@theme {
/* gray */
--color-gray-50: oklch(0.98559 0.002 48.697);
--color-gray-100: oklch(0.95 0.001 48.697);
--color-gray-200: oklch(0.925 0.002 48.697);
--color-gray-300: oklch(0.84598 0.002 48.697);
--color-gray-400: oklch(0.7374 0.002 48.697);
--color-gray-500: oklch(0.585 0.003 48.697);
--color-gray-600: oklch(0.46 0.002 48.697);
--color-gray-700: oklch(0.37 0.002 48.697);
--color-gray-800: oklch(0.31 0.001 48.697);
--color-gray-900: oklch(0.265 0.001 48.697);
--color-gray-950: oklch(0.21 0.002 48.697);
/* red */
--color-red-50: oklch(0.96453 0.015 24);
--color-red-100: oklch(0.95036 0.03 24);
--color-red-200: oklch(0.92601 0.045 24);
--color-red-300: oklch(0.8455 0.07 24);
--color-red-400: oklch(0.73813 0.135 24);
--color-red-500: oklch(0.63497 0.18 24);
--color-red-600: oklch(0.50144 0.15 24);
--color-red-700: oklch(0.38998 0.125 24);
--color-red-800: oklch(0.30985 0.095 24);
--color-red-900: oklch(0.26461 0.06 24);
--color-red-950: oklch(0.21016 0.04 24);
/* orange */
--color-orange-50: oklch(0.96453 0.02 41.5);
--color-orange-100: oklch(0.95036 0.025 41.5);
--color-orange-200: oklch(0.92601 0.03 41.5);
--color-orange-300: oklch(0.8455 0.07 41.5);
--color-orange-400: oklch(0.73813 0.14 41.5);
--color-orange-500: oklch(0.63497 0.17 41.5);
--color-orange-600: oklch(0.50144 0.135 41.5);
--color-orange-700: oklch(0.38998 0.08 41.5);
--color-orange-800: oklch(0.30985 0.065 41.5);
--color-orange-900: oklch(0.26461 0.055 41.5);
--color-orange-950: oklch(0.21016 0.04 41.5);
}
@layer base {
:root {
color-scheme: light;
--background: oklch(0.98559 0.002 48.697);
--foreground: oklch(0.265 0.001 48.697);
--card: oklch(0.95 0.001 48.697);
--card-foreground: oklch(0.265 0.001 48.697);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.265 0.001 48.697);
--dialog: oklch(0.98559 0.002 48.697);
--dialog-foreground: oklch(0.265 0.001 48.697);
--primary: oklch(0.265 0.001 48.697);
--primary-foreground: oklch(1 0 0);
--secondary: oklch(0.21 0.002 48.697 / 0.08);
--secondary-foreground: oklch(0.265 0.001 48.697);
--muted: oklch(0.95 0.001 48.697);
--muted-foreground: oklch(0.585 0.003 48.697);
--accent: oklch(0.63497 0.17 41.5);
--accent-foreground: oklch(1 0 0);
--destructive: oklch(0.63497 0.18 24);
--destructive-foreground: oklch(1 0 0);
--border: oklch(0.21 0.002 48.697 / 0.1);
--ring: oklch(0.63497 0.17 41.5 / 0.3);
--field: oklch(0.21 0.002 48.697 / 0.05);
/* surface state variants — derived from each base token via color-mix.
Opaque surfaces mix toward --foreground (darken in light, lighten in
dark); primary mixes toward its own foreground (the inverse). These are
theme-agnostic and inherited by [data-theme="dark"]. Only --secondary
stays per-theme: it's a translucent overlay tint, so its states bump
alpha rather than mixing to an opaque color. */
--background-hover: color-mix(in oklch, var(--background), var(--foreground) 6%);
--background-active: color-mix(in oklch, var(--background), var(--foreground) 12%);
--card-hover: color-mix(in oklch, var(--card), var(--foreground) 6%);
--card-active: color-mix(in oklch, var(--card), var(--foreground) 12%);
--popover-hover: color-mix(in oklch, var(--popover), var(--foreground) 6%);
--popover-active: color-mix(in oklch, var(--popover), var(--foreground) 12%);
--dialog-hover: color-mix(in oklch, var(--dialog), var(--foreground) 6%);
--dialog-active: color-mix(in oklch, var(--dialog), var(--foreground) 12%);
--muted-hover: color-mix(in oklch, var(--muted), var(--foreground) 6%);
--muted-active: color-mix(in oklch, var(--muted), var(--foreground) 12%);
--primary-hover: color-mix(in oklch, var(--primary), var(--primary-foreground) 15%);
--primary-active: color-mix(in oklch, var(--primary), var(--primary-foreground) 26%);
--accent-hover: color-mix(in oklch, var(--accent), var(--foreground) 12%);
--accent-active: color-mix(in oklch, var(--accent), var(--foreground) 22%);
--destructive-hover: color-mix(in oklch, var(--destructive), var(--foreground) 12%);
--destructive-active: color-mix(in oklch, var(--destructive), var(--foreground) 22%);
--secondary-hover: oklch(0.21 0.002 48.697 / 0.16);
--secondary-active: oklch(0.21 0.002 48.697 / 0.22);
--chart-1: var(--orange-500, oklch(0.63497 0.17 41.5));
--chart-2: var(--red-500, oklch(0.63497 0.18 24));
--chart-3: oklch(0.585 0.003 48.697);
--chart-4: oklch(0.73813 0.14 41.5);
--chart-5: oklch(0.8455 0.07 24);
--sidebar: var(--card);
--sidebar-foreground: var(--foreground);
--sidebar-primary: var(--primary);
--sidebar-primary-foreground: var(--primary-foreground);
--sidebar-accent: var(--secondary);
--sidebar-accent-foreground: var(--secondary-foreground);
--sidebar-border: var(--border);
--sidebar-ring: var(--ring);
}
:root[data-theme="dark"],
.dark {
color-scheme: dark;
--background: oklch(0.21 0.002 48.697);
--foreground: oklch(0.95 0.001 48.697);
--card: oklch(0.21 0.002 48.697);
--card-foreground: oklch(0.95 0.001 48.697);
--popover: oklch(0.265 0.001 48.697);
--popover-foreground: oklch(0.95 0.001 48.697);
--dialog: oklch(0.265 0.001 48.697);
--dialog-foreground: oklch(0.95 0.001 48.697);
--primary: oklch(0.95 0.001 48.697);
--primary-foreground: oklch(0.265 0.001 48.697);
--secondary: oklch(0.98559 0.002 48.697 / 0.08);
--secondary-foreground: oklch(0.95 0.001 48.697);
--muted: oklch(0.31 0.001 48.697);
--muted-foreground: oklch(0.7374 0.002 48.697);
--accent: oklch(0.50144 0.135 41.5);
--accent-foreground: oklch(1 0 0);
--destructive: oklch(0.63497 0.18 24);
--destructive-foreground: oklch(1 0 0);
--border: oklch(0.98559 0.002 48.697 / 0.1);
--ring: oklch(0.63497 0.17 41.5 / 0.3);
/* darker well in dark mode — same gray-950 base, higher alpha */
--field: oklch(0.21 0.002 48.697 / 0.3);
/* surface state variants — opaque tokens inherit the color-mix
derivations from :root and re-resolve against the dark base/foreground.
Only the translucent --secondary overlay is redefined per-theme. */
--secondary-hover: oklch(0.98559 0.002 48.697 / 0.16);
--secondary-active: oklch(0.98559 0.002 48.697 / 0.22);
--chart-1: oklch(0.50144 0.135 41.5);
--chart-2: oklch(0.63497 0.18 24);
--chart-3: oklch(0.7374 0.002 48.697);
--chart-4: oklch(0.38998 0.08 41.5);
--chart-5: oklch(0.8455 0.07 24);
--sidebar: var(--card);
--sidebar-foreground: var(--foreground);
--sidebar-primary: var(--primary);
--sidebar-primary-foreground: var(--primary-foreground);
--sidebar-accent: var(--secondary);
--sidebar-accent-foreground: var(--secondary-foreground);
--sidebar-border: var(--border);
--sidebar-ring: var(--ring);
}
* {
border-color: var(--border);
outline-color: color-mix(in oklab, var(--ring) 50%, transparent);
}
body {
color: var(--foreground);
background: var(--background);
}
}
@theme inline {
--color-background: var(--background);
--color-background-hover: var(--background-hover);
--color-background-active: var(--background-active);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-card-hover: var(--card-hover);
--color-card-active: var(--card-active);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-popover-hover: var(--popover-hover);
--color-popover-active: var(--popover-active);
--color-dialog: var(--dialog);
--color-dialog-foreground: var(--dialog-foreground);
--color-dialog-hover: var(--dialog-hover);
--color-dialog-active: var(--dialog-active);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-primary-hover: var(--primary-hover);
--color-primary-active: var(--primary-active);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary-hover: var(--secondary-hover);
--color-secondary-active: var(--secondary-active);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-muted-hover: var(--muted-hover);
--color-muted-active: var(--muted-active);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-accent-hover: var(--accent-hover);
--color-accent-active: var(--accent-active);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-destructive-hover: var(--destructive-hover);
--color-destructive-active: var(--destructive-active);
--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-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);
--color-border: var(--border);
--color-field: var(--field);
--color-ring: var(--ring);
}