• Color Palette
  • Accessibility
  • Brand Colors
  • Design Tokens

From Brand Primary to Accessible Palette: Tints, Shades, and Semantic Roles

Turn one brand seed color into text, surface, border, and semantic tokens you can validate for WCAG contrast and export to CSS or Figma.

Sherotree

·7 min read

Most brand decks hand designers a single hero hex—something like a vivid indigo or coral—and a vague instruction to “build the system around it.” The gap between that seed and a usable product palette is where contrast failures hide: light primary on white for links, soft danger backgrounds with soft danger text, borders that vanish on cards.

This article walks an original seed-to-roles workflow: define the seed, generate tints and shades, assign semantic roles, validate pairs, then export notes for CSS and Figma tokens. All tools linked here are free browser utilities on Uwarp.

Start with one honest seed

Pick the color marketing actually owns—the logo mark, the campaign CTA, or the product illustration accent. Write it down as sRGB hex. Example seed used throughout this article (invented for teaching):

Seed primary: #5B4DFF

Resist the urge to start with five brand colors. One seed plus neutrals plus a small semantic set (danger, warning, success) covers most SaaS and marketing sites. Extra brand hues can wait until the primary scale is readable.

Questions to answer before you generate steps

  1. Will primary appear as large filled buttons, inline text links, or both?
  2. Do you need a light theme only, or light + dark?
  3. Is the product AA (4.5:1 normal text) enough, or do you also target AAA for long-form reading?

If the answer to (1) is “both,” you almost certainly need two primary-related text colors: one that sits on the primary fill, and one that sits on white/gray surfaces as a link color.

Build a scale: tints, mid, shades

A practical product scale is not a perfect HSL ramp. It is a set of steps with jobs:

Step (example names)Job
primary-50 … primary-200Soft surfaces, selected rows, chips
primary-300 … primary-400Illustrative accents, large decorative shapes
primary-500 / seedBrand recognition on big CTAs
primary-600 … primary-700Stronger fills, hover on light theme
primary-800 … primary-900Text links and small UI on light backgrounds

Generate candidates with Accessible Brand Colors. Treat the output as a proposal, not a final token list. Your job is to assign roles and kill steps that do not earn a job.

For exploration alongside brand-focused generators, the ColorDesigner curated set and the wider color tools category are useful when you need alternate ramps or supporting utilities—still validate every pair you ship.

Assign semantic roles (not just “more purple”)

Roles beat step numbers in handoff docs. Map colors to meaning:

Core roles

  • Text / default — main reading color on surfaces (usually near-black or deep gray, not the brand seed).
  • Text / muted — secondary copy; still ≥4.5:1 on the surfaces it uses.
  • Surface / default — page background.
  • Surface / raised — cards, popovers.
  • Border / subtle and Border / strong — structure vs input outlines.
  • Primary / fill, Primary / fill-hover, Primary / text-on-fill, Primary / text-on-surface (link).
  • Danger / fill, Danger / text, Danger / surface — errors and destructive actions.
  • Optional: Warning and Success with the same fill / text / surface split.

Role rule that prevents most failures

Never use the same hex for soft surface and message text. Soft rose backgrounds need a darker red for the sentence “Payment failed.” Soft violet selection rows need a darker violet (or neutral) for the selected label if that label is small.

Validate pairs: accessible-brand-colors + contrast checker

Pass 1 — structural pairs

For each role that includes text or icons, list the background it sits on:

Foreground roleBackground roleTarget
Text / defaultSurface / default≥4.5:1 (AA normal)
Text / mutedSurface / default & raised≥4.5:1
Primary / text-on-surfaceSurface / default≥4.5:1
Primary / text-on-fillPrimary / fill≥4.5:1 (button label)
Danger / textDanger / surface and Surface / default≥4.5:1
Border / strong (inputs)Surface / defaultaim ~3:1 vs adjacent surface for clarity

Pass 2 — tool workflow

  1. Draft the scale in Accessible Brand Colors from your seed.
  2. Copy the candidate hexes into a scratch table (Notion, spreadsheet, or design tokens file).
  3. Open the Colour Contrast Checker.
  4. Test every row in the structural table above.
  5. When a pair fails, adjust one role at a time: usually darken text-on-surface, or lighten / darken the fill under white labels—not both randomly.
  6. Re-check hover and focus if those states change fill.

Invented before/after for the sample seed

Before (common mistake):

  • Link text: #5B4DFF on #FFFFFF — may pass or sit close depending on exact computation; on #F5F3FF it often gets worse.
  • Button: white label on #5B4DFF — check explicitly; many vivid purples pass for large buttons and still feel harsh for small UI.
  • Danger text: #F87171 on #FEF2F2 — frequently fails AA for body-sized errors.

After (role split):

  • primary-text-on-surface: #4338CA (darker sibling for links)
  • primary-fill: #5B4DFF
  • primary-text-on-fill: #FFFFFF (confirm ≥4.5:1)
  • primary-surface: #EEF2FF
  • danger-text: #B91C1C
  • danger-surface: #FEF2F2

Re-run each pair in the contrast checker before you lock tokens.

Neutrals are half the system

Brand scales get attention; neutrals carry reading. Define at least:

  • neutral-0 / white
  • neutral-50–neutral-200 surfaces
  • neutral-400–neutral-500 borders and disabled chrome (carefully)
  • neutral-700–neutral-900 text

Muted text should be a neutral that still clears 4.5:1 on your lightest common surface—not “whatever looks secondary in the mock.”

Export notes for CSS and Figma tokens

CSS custom properties

Name by role, alias steps underneath:

:root {
  --color-primary-fill: #5b4dff;
  --color-primary-fill-hover: #4f46e5;
  --color-primary-text-on-fill: #ffffff;
  --color-primary-text-on-surface: #4338ca;
  --color-primary-surface: #eef2ff;
 
  --color-text-default: #111827;
  --color-text-muted: #4b5563;
  --color-surface-default: #ffffff;
  --color-surface-raised: #f9fafb;
  --color-border-subtle: #e5e7eb;
  --color-border-strong: #9ca3af;
 
  --color-danger-text: #b91c1c;
  --color-danger-surface: #fef2f2;
}

Keep raw ramp variables (--color-primary-500) if engineers want them, but components should consume roles so a contrast fix is one token change.

Figma

  • Create a Color collection with modes for Light / Dark if you ship both.
  • Mirror the same role names as CSS.
  • Document in the file description: “Text-on-surface primary is not the marketing seed; seed is fill.”
  • After token updates, spot-check a key frame with the browser contrast checker—plugins help inside Figma, but a second tool catches export typos.

Hand-off checklist

  • Seed hex recorded; primary fill and primary text-on-surface are separate tokens.
  • Soft surfaces never reuse message text colors.
  • AA pairs documented for default text, muted text, primary link, primary button label, danger text.
  • CSS and Figma use the same role names.
  • Dark mode (if any) re-validated, not only inverted.

A short workflow you can reuse on every brand

  1. Capture seed → Accessible Brand Colors.
  2. Assign roles (table above).
  3. Validate with Colour Contrast Checker.
  4. Export role-based CSS / Figma tokens.
  5. Only then expand into secondary brand hues.

That order keeps marketing’s favorite color visible on CTAs without forcing every paragraph link to fight WCAG.

Suggested next article

For decorative backgrounds that sit behind this palette, see Choosing Free SVG Background Generators—and always re-check type contrast when blobs or grain land under text.