Build a Design System From One Color
One brand color becomes five ramps and a full semantic token layer for light and dark mode — exportable as CSS, a Tailwind v4 theme, JSON, Figma variables, SCSS or TypeScript.
Semantic tokens in context
Surfaces, borders, text and interactive fills all derived from one hue.
:root {
color-scheme: light dark;
--primary-50: oklch(0.971 0.014 277.1);
--primary-100: oklch(0.936 0.031 277.1);
--primary-200: oklch(0.885 0.056 277.1);
--primary-300: oklch(0.808 0.097 277.1);
--primary-400: oklch(0.712 0.151 277.1);
--primary-500: oklch(0.637 0.197 277.1);
--primary-600: oklch(0.577 0.21 277.1);
--primary-700: oklch(0.505 0.197 277.1);
--primary-800: oklch(0.444 0.176 277.1);
--primary-900: oklch(0.396 0.159 277.1);
--primary-950: oklch(0.281 0.111 277.1);
--neutral-50: oklch(0.971 0.001 277.1);
--neutral-100: oklch(0.936 0.002 277.1);
--neutral-200: oklch(0.885 0.004 277.1);
--neutral-300: oklch(0.808 0.007 277.1);
--neutral-400: oklch(0.712 0.01 277.1);
--neutral-500: oklch(0.637 0.013 277.1);
--neutral-600: oklch(0.577 0.012 277.1);
--neutral-700: oklch(0.505 0.012 277.1);
--neutral-800: oklch(0.444 0.01 277.1);
--neutral-900: oklch(0.396 0.009 277.1);
--neutral-950: oklch(0.281 0.007 277.1);
--success-50: oklch(0.971 0.012 150);
--success-100: oklch(0.936 0.027 150);
--success-200: oklch(0.885 0.055 150);
--success-300: oklch(0.808 0.1 150);
--success-400: oklch(0.712 0.148 150);
--success-500: oklch(0.637 0.175 150);
--success-600: oklch(0.577 0.159 150);
--success-700: oklch(0.505 0.139 150);
--success-800: oklch(0.444 0.122 150);
--success-900: oklch(0.396 0.109 150);
--success-950: oklch(0.281 0.077 150);
--warning-50: oklch(0.971 0.01 75);
--warning-100: oklch(0.936 0.021 75);
--warning-200: oklch(0.885 0.043 75);
--warning-300: oklch(0.808 0.077 75);
--warning-400: oklch(0.712 0.114 75);
--warning-500: oklch(0.637 0.134 75);
--warning-600: oklch(0.577 0.122 75);
--warning-700: oklch(0.505 0.107 75);
--warning-800: oklch(0.444 0.094 75);
--warning-900: oklch(0.396 0.084 75);
--warning-950: oklch(0.281 0.059 75);
--danger-50: oklch(0.971 0.012 27);
--danger-100: oklch(0.936 0.027 27);
--danger-200: oklch(0.885 0.055 27);
--danger-300: oklch(0.808 0.1 27);
--danger-400: oklch(0.712 0.148 27);
--danger-500: oklch(0.637 0.179 27);
--danger-600: oklch(0.577 0.175 27);
--danger-700: oklch(0.505 0.164 27);
--danger-800: oklch(0.444 0.146 27);
--danger-900: oklch(0.396 0.132 27);
--danger-950: oklch(0.281 0.093 27);
--color-background: oklch(0.971 0.001 277.1);
--color-surface: oklch(1 0 0);
--color-surface-raised: oklch(0.936 0.002 277.1);
--color-border: oklch(0.885 0.004 277.1);
--color-text: oklch(0.396 0.009 277.1);
--color-text-muted: oklch(0.577 0.012 277.1);
--color-primary: oklch(0.637 0.197 277.1);
--color-primary-hover: oklch(0.577 0.21 277.1);
--color-primary-active: oklch(0.505 0.197 277.1);
--color-primary-subtle: oklch(0.936 0.031 277.1);
--color-primary-foreground: oklch(0 0 0);
--color-success: oklch(0.577 0.159 150);
--color-warning: oklch(0.637 0.134 75);
--color-danger: oklch(0.577 0.175 27);
/* Single-property automatic theming */
--color-background-auto: light-dark(oklch(0.971 0.001 277.1), oklch(0.281 0.007 277.1));
--color-surface-auto: light-dark(oklch(1 0 0), oklch(0.396 0.009 277.1));
--color-surface-raised-auto: light-dark(oklch(0.936 0.002 277.1), oklch(0.444 0.01 277.1));
--color-border-auto: light-dark(oklch(0.885 0.004 277.1), oklch(0.444 0.01 277.1));
--color-text-auto: light-dark(oklch(0.396 0.009 277.1), oklch(0.971 0.001 277.1));
--color-text-muted-auto: light-dark(oklch(0.577 0.012 277.1), oklch(0.712 0.01 277.1));
--color-primary-auto: light-dark(oklch(0.637 0.197 277.1), oklch(0.712 0.151 277.1));
--color-primary-hover-auto: light-dark(oklch(0.577 0.21 277.1), oklch(0.808 0.097 277.1));
--color-primary-active-auto: light-dark(oklch(0.505 0.197 277.1), oklch(0.885 0.056 277.1));
--color-primary-subtle-auto: light-dark(oklch(0.936 0.031 277.1), oklch(0.396 0.159 277.1));
--color-primary-foreground-auto: light-dark(oklch(0 0 0), oklch(0 0 0));
--color-success-auto: light-dark(oklch(0.577 0.159 150), oklch(0.712 0.148 150));
--color-warning-auto: light-dark(oklch(0.637 0.134 75), oklch(0.712 0.114 75));
--color-danger-auto: light-dark(oklch(0.577 0.175 27), oklch(0.712 0.148 27));
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: oklch(0.281 0.007 277.1);
--color-surface: oklch(0.396 0.009 277.1);
--color-surface-raised: oklch(0.444 0.01 277.1);
--color-border: oklch(0.444 0.01 277.1);
--color-text: oklch(0.971 0.001 277.1);
--color-text-muted: oklch(0.712 0.01 277.1);
--color-primary: oklch(0.712 0.151 277.1);
--color-primary-hover: oklch(0.808 0.097 277.1);
--color-primary-active: oklch(0.885 0.056 277.1);
--color-primary-subtle: oklch(0.396 0.159 277.1);
--color-primary-foreground: oklch(0 0 0);
--color-success: oklch(0.712 0.148 150);
--color-warning: oklch(0.712 0.114 75);
--color-danger: oklch(0.712 0.148 27);
}
}
[data-theme="dark"] {
--color-background: oklch(0.281 0.007 277.1);
--color-surface: oklch(0.396 0.009 277.1);
--color-surface-raised: oklch(0.444 0.01 277.1);
--color-border: oklch(0.444 0.01 277.1);
--color-text: oklch(0.971 0.001 277.1);
--color-text-muted: oklch(0.712 0.01 277.1);
--color-primary: oklch(0.712 0.151 277.1);
--color-primary-hover: oklch(0.808 0.097 277.1);
--color-primary-active: oklch(0.885 0.056 277.1);
--color-primary-subtle: oklch(0.396 0.159 277.1);
--color-primary-foreground: oklch(0 0 0);
--color-success: oklch(0.712 0.148 150);
--color-warning: oklch(0.712 0.114 75);
--color-danger: oklch(0.712 0.148 27);
}
What you get
The generator derives a neutral ramp from your brand hue at very low chroma, so greys feel related to the brand instead of dead. Feedback hues are pinned to conventional positions (green success, amber warning, red danger) at matched lightness and chroma so they read as one family.
Paste-and-go in ten seconds
The Tailwind export uses light-dark() for semantic tokens, so a single declaration covers both themes and color-scheme handles the switch. The CSS export additionally ships a prefers-color-scheme block and a [data-theme="dark"] override for apps with a manual toggle.
Naming your tokens
Ramp steps (--primary-500) are your palette. Semantic tokens (--color-surface, --color-text-muted) are what components should consume. Keeping that separation is what lets you re-theme an entire product by changing one hue.
Frequently asked questions
- What does “build a design system from one color” actually generate?
- Five 50–950 ramps (primary, neutral, success, warning, danger) plus a semantic token layer — background, surface, border, text, text-muted, primary, primary-hover, primary-active, primary-subtle and feedback colors — resolved for both light and dark mode.
- Can I paste the output straight into a Tailwind v4 project?
- Yes. The Tailwind export is a complete @theme block using oklch() values and light-dark() for the semantic layer, so bg-primary-500 and var(--color-surface) work immediately after pasting into your entry stylesheet.
- How do the Figma variables work?
- The Figma export is a JSON variable collection with Light and Dark modes and HEX values per mode, matching the shape variable-import plugins expect. Ramp steps land under primary/500 style names and semantic tokens under semantic/*.
- Are the semantic pairings accessible?
- Text and surface pairings are checked with WCAG contrast and reported below, and foreground colors for primary fills are chosen by contrast. Always re-test any pairing you customise afterwards.