kuler .ai
Home / Blog / From a Hex Code to a Full Design System in 10 Minutes
Design Systems Workflow

From a Hex Code to a Full Design System in 10 Minutes

You have a brand color. Now what? A step-by-step guide to expanding a single hex value into a complete, production-ready color system.

· 7 min read

The single-color starting point

Every design system starts the same way: someone picks a color. Maybe it's from a logo, a mood board, or a client's email that says "we like this blue." You have one hex code and a blank canvas.

The gap between that single color and a production-ready system — with semantic roles, dark mode support, accessible pairings, and scalable structure — is where most projects stall. Not because it's hard, but because the steps aren't obvious.

Here's the exact workflow.

Step 1: Convert to OKLCH and understand what you have

Take your hex and convert it to OKLCH. This gives you three values that actually mean something:

/* Starting color: #4F46E5 (indigo) */
oklch(0.49 0.22 264)

L = 0.49 → mid-range lightness (slightly dark)
C = 0.22 → high chroma (vivid, saturated)
H = 264  → blue-violet hue

These values tell you the character of your brand color. High chroma means it's bold and attention-grabbing. Mid lightness means it works well as a primary action color but needs lighter and darker variants. The hue places it in the cool spectrum.

Step 2: Generate a lightness scale

A single color isn't a system. You need a full scale — typically 11 steps from near-white to near-black — that lets you reach for the right weight in any context.

In OKLCH, this is straightforward: keep the hue constant, keep chroma proportional, and step evenly through lightness:

--primary-50:  oklch(0.97 0.03 264);  /* tinted background */
--primary-100: oklch(0.93 0.05 264);  /* hover background */
--primary-200: oklch(0.87 0.08 264);  /* subtle highlight */
--primary-300: oklch(0.78 0.12 264);  /* border, badge bg */
--primary-400: oklch(0.67 0.17 264);  /* muted text on dark */
--primary-500: oklch(0.49 0.22 264);  /* ← your brand color */
--primary-600: oklch(0.43 0.20 264);  /* hover state */
--primary-700: oklch(0.37 0.18 264);  /* pressed state */
--primary-800: oklch(0.29 0.14 264);  /* dark text */
--primary-900: oklch(0.22 0.10 264);  /* heading on light */
--primary-950: oklch(0.15 0.06 264);  /* near-black */

Notice how chroma decreases at the extremes. Very light and very dark colors can't hold high chroma — they'd look neon or muddy. Tapering chroma naturally is what keeps the scale looking professional.

Step 3: Define semantic roles

Raw scales are tools. Semantic roles are the interface your components actually use. Map your scale values to roles:

  • Primary — Your brand color (500). Buttons, links, focus rings.
  • Primary Hover — One step darker (600). Interactive feedback.
  • Primary Subtle — Very light tint (50 or 100). Badge backgrounds, selected row highlights.
  • Background — Neutral, near-white. Usually not from your brand scale — use a neutral with a slight hue tint.
  • Surface — Cards, panels. Pure white or a very subtle tint.
  • Text — Near-black. Can be from your brand's 900/950 for a cohesive feel, or a pure neutral for maximum readability.
  • Muted — Secondary text, timestamps, placeholders. Gray with optional brand tint.
  • Border — Dividers and outlines. Light gray, slightly warmer than pure gray.

Step 4: Add a complementary accent

One color isn't enough for a real interface. You need at least one accent color for secondary actions, success states, or visual variety. The fastest way to find one that works:

  • Complementary — Rotate hue 180°. For H=264, that's H=84 (a warm yellow-green). High contrast, high energy.
  • Analogous — Rotate hue 30°. For H=264, that's H=294 (purple) or H=234 (blue). Harmonious, low tension.
  • Split complementary — Rotate hue ±150°. Gives two accent options that both contrast with the primary without being as intense as a direct complement.

Pick one, generate its own lightness scale, and assign it to accent semantic roles. For most products, one primary + one accent covers 90% of use cases.

Step 5: Build the dark mode variant

With OKLCH, dark mode is a systematic transformation, not a redesign:

  • Backgrounds — Flip to near-black. Use your brand hue at very low chroma for tinting: oklch(0.13 0.01 264).
  • Surfaces — Slightly lighter than background: oklch(0.16 0.01 264).
  • Text — Flip to near-white: oklch(0.93 0.01 264).
  • Primary — Increase lightness slightly, decrease chroma slightly. The color should be recognizable but less intense.
  • Borders — Very subtle light edges: oklch(0.22 0.01 264).

The rule of thumb: in dark mode, lightness values roughly mirror around 0.50, and chroma drops by 15–25%.

Step 6: Verify contrast ratios

Before you ship, check every text-on-background pair:

  • Text on background — must pass 4.5:1 (AA)
  • Muted text on surface — must pass 4.5:1
  • Primary on primary-subtle — check this; it often fails
  • All of the above in dark mode too

If a pair fails, adjust the lighter color's lightness up or the darker one's down. In OKLCH, you know exactly which direction to move and by how much.

Step 7: Export and ship

Package the result as CSS custom properties, a Tailwind config, or an AI rules file. The format depends on your stack, but the structure is the same: semantic names mapping to OKLCH values, with light and dark variants.

On kuler.ai, this entire workflow — from a starting palette to a rules file — is a few clicks. Pick a palette, export for your tool, and you have a complete color context file that your AI coding tool, design system, or component library can consume immediately.

A design system doesn't need a hundred colors. It needs six well-chosen ones, systematically expanded, and rigorously verified.
Back to blog