Web DesignBy Sagar Chachapara · July 28, 2026 · 13 min read

How to Build a Design System Color Palette From a Single Brand Color

Turn one brand hex code into a full design system palette: tint and shade ramps, neutral scales, semantic colors, dark mode, and export-ready tokens — step by step.

The brief arrives with confidence and one line: "Our brand color is #2563EB. Make it work." No neutrals, no secondary palette, no dark mode. The good news: expanding one color into a complete system is mostly mechanical — you build lightness ramps for the hue, a desaturated neutral scale, semantic tokens for states, and a dark-mode set, then export them as code tokens. None of it requires taste; it requires a reliable process. This guide runs the whole pipeline on #2563EB, hands you the tokened result, and flags the steps where people usually stop and improvise.

Step 1 — Build a lightness ramp, not "a few variations"

Never hard-code five random colors. Build a ramp by holding hue and saturation roughly constant and walking lightness upward and downward from your base.

Ramp for #2563EB:

  • #EFF4FF — 50 (brand-50)
  • #D9E4FC — 200
  • #9DB8F5 — 300
  • #5D8BF0 — 400
  • #2563EB — 500 (the brand color itself)
  • #1D4ED8 — 600
  • #1E40AF — 700
  • #1E3A8A — 800
  • #172554 — 900

The 50/100 end is for tinted backgrounds and badges; the 900 end is for deep headers and on-light text. This ramp alone covers buttons, links, focus rings, chips, and table headers.

Step 2 — Build a neutral scale that isn't gray-blue

Pure grays will clash with a blue brand. Your neutrals should carry the brand's undertone — mix a touch of the brand hue into the gray — while staying distinct enough to read as "neutral."

Neutral "ink" scale for a blue system:

  • #FFFFFF — surface absolute
  • #F8FAFD — surface
  • #EFF2F7 — surface raised
  • #CBD5E1 — border
  • #64748B — muted text / icons
  • #334155 — secondary text
  • #0F172A — primary text (ink)

Test critically: #64748B on #F8FAFD is about 4.9:1 — passes AA for body text. #CBD5E1 is decoration only; never text.

Step 3 — Add semantic colors that can't collide

Semantic tokens are about meaning, not aesthetics: success, warning, danger, and info must stay visually distinct from each other and from the primary brand color. A classic failure: primary blue and info blue look identical; danger red and warning orange blur at 88% screen brightness.

Semantic set:

  • Success #16A34A (dark mode #22C55E)
  • Warning #D97706 (dark #F59E0B)
  • Danger #DC2626 (dark #EF4444)
  • Info #0284C7 (dark #38BDF8)
  • Focus ring #1D4ED8

The dark-mode variants exist because raw #16A34A on dark surfaces reads muddy; brighter dark-mode fags fix legibility without changing meaning.

Step 4 — Generate the dark-mode skin from the same ramp

Don't invent a new palette for dark mode — remap the ramp. On dark surfaces, the 900s become background, the 300s become text, and the primary accent rises to the 400/500 band to hold contrast.

Dark remap for this system:

  • Background #0F172A, card #172038, raised #1E293B
  • Primary text #F1F5F9, secondary #94A3B8
  • Primary action #3B82F6 (brand-400), focus ring #60A5FA

Rule: keep the lexicon identical — surface, text, primary, danger all still exist; only their values swap. That's what lets engineers reuse tokens and what keeps the design system consistent in both themes.

Don't give every surface the same dark fill. Dark mode needs elevation by lightness: cards sit one step lighter than the page background, hover states one step lighter still, and modal overlays a step up again — otherwise your UI flattens into a single black slab. The three dark surfaces above (#0F172A, #172038, #1E293B) are that elevation story in miniature, and it's the difference between a dark mode that feels layered and one that feels like one giant rectangle.

Step 5 — Export as tokens, not as Figma fill colors

Design systems fail when the values live in a deck and not in code. Produce three artifacts: CSS custom properties, a Tailwind colors extension, and a JSON token file. Palette 3 — the deliverable shape:

:root {
  --color-primary: #2563eb;
  --color-primary-600: #1d4ed8;
  --color-surface: #f8fafd;
  --color-text: #0f172a;
  --color-muted: #64748b;
  --color-success: #16a34a;
}

Step 6 — Name tokens like a product, not a color picker

Naming is where systems quietly rot. If your team references colors as "brand-blue-500" and "darker-blue," nothing reusable survives a rebrand. Name tokens by role, and only keep the ramp names as a second, technical layer:

  • color.brand.primary, color.brand.primary-hover, color.brand.primary-active
  • color.surface.default, color.surface.raised, color.surface.overlay
  • color.text.primary, color.text.secondary, color.text.disabled
  • color.border.default, color.status.success, color.status.danger

The rule I enforce: a designer should be able to change status.success and every success state updates automatically, without anyone hunting for a fill. If your names map to jobs ("success", "active", "muted") rather than hues ("blue2"), you can re-theme the entire product in an afternoon — worth it precisely because the brand team changes its mind every quarter.

From ramp to reality: a sample migration

Theory gets concrete when you watch one small screen get converted. Take a login card that currently hard-codes #4A90E2, #F5F7FA, and #333333:

  1. Replace the text color. #333333 → color.text.primary (resolves to #0F172A). Everything that was gray becomes the system ink, and the 15:1 contrast comes for free.
  2. Replace the surface. #F5F7FA → color.surface.default (#F8FAFD). Close enough visually that users see no change, but the card now follows theme.
  3. Replace the button. #4A90E2 → color.brand.primary (#2563EB), plus new hover/active and disabled tokens it never had. The disabled state, previously a forgotten gray, gets a real semantic value.
  4. Add the focus ring. color.focus.ring (#1D4ED8) goes on the email and password fields — an accessibility upgrade that costs one line.
  5. Ship dark mode. Because every value is a token, the dark remap from Step 4 applies to this screen with zero further edits. The whole migration is an afternoon; the same change on an untokened product is a week of hunting #4A90E2 in design files and CSS.

That's the pitch for this process: the ramp gives you the product, the naming gives you the system, and the tokens give you dark mode and rebrands for free.

The four flaws that kill most of these systems

  1. Too many blues. Five blues where three would do — primary, dark, and light — and each new one gets used inconsistently. Push for a tight ramp; looseness is the enemy.
  2. Neutrals that read "blue" when the app is supposed to feel neutral. If your gray looks blue next to a warm accent, temperature it with a warmer neutral for contrast in the KYC/file flows.
  3. Semantic colors too close to the brand hue. If success and primary are the same hue family, colorblind users can't tell "confirmed" from "interactive." Add shape/label backup regardless.
  4. Text tokens chosen by eye. #64748B-on-white passes; #94A3B8-on-white fails (≈2.9:1). Any token destined for text needs the 4.5:1 check, in both themes.

Frequently asked questions

How many steps should my ramp have?

5 is enough to ship a Lean design system; 9–10 gives you the full tailwind-style range (50–900). Start with 50, 200, 400, 500, 600, 700, 900 and expand only when a real screen asks for a missing step.

Should every brand hue become a full ramp?

Every product color should. Whitespace and hero decorations can be flat brand fills; anything used as text, surface, or state needs the ramp mechanics so contrast math holds.

Isn't this just Tailwind's default palette with my hue on top?

Functionally yes — and that's fine. The value you add is your ramp, your boundaries, and your dark-mode mapping, not inventing a proprietary scale. Copying the pattern and swapping in your hue is exactly right.

How do I keep the palette stable as the brand manual changes?

Tokenize everything and treat the raw hex as a reference, not the source of truth. When brand ships a new primary, you regenerate ramps from it, version the JSON, and let designers re-import from the tokenized source rather than manually re-picking fills.

What's the smallest system that still counts as a design system?

Two ramps, one neutral family, and a semantic layer. Concretely: your brand ramp at 5 steps, a warm/cool neutral ramp, plus text/border/success/danger tokens. Everything above that — elevation, focus ring, dark-mode values — is refinement. If your team can change the brand hue in one JSON file and every component follows, you have a system; the rest is polish.

Generate your own system from your brand color

Start from any hex in the palette generator — lock the hue and reshuffle until you like the ramp — then run it through the palette exporter to get the CSS variables, Tailwind config, and JSON tokens in one export, ready to drop into your repo.

Written by Sagar Chachapara

Founder & Developer of Color Palette — Sagar Chachapara is an independent developer who builds color tools and writes about color theory, accessible design, and working with palettes in the browser.

sagarchachapara1997@gmail.com

Put it into practice

Try the techniques from this guide in our free tools — no sign-up required.

More in Web Design · All articles · FAQ

Keep reading