Skip to content
ColorKit

Free color scheme generator

One brand color into a working interface scheme

Paste a brand hex and this builds the set a front end actually consumes: ten tokens named for the job they do rather than for a position on a wheel, in a light or a dark variant, with the neutrals carrying as much or as little of your hue as you want. Every role that has an accessibility obligation is solved for rather than guessed — lightness is stepped half a point at a time in LCH until the pair clears its threshold — so the muted text, the control outline and the accent pass by construction. Free with no signup, and the finished scheme copies out as a :root block of custom properties.

  • 100% free
  • No signup
  • 10 named roles
  • Light and dark
  • 6 pairs scored
Surface mode

Press Ctrl+V anywhere on the page to drop in the brand hex the whole scheme is derived from — no need to click the field first.

Ten roles, not ten colors

  • --bgPage behind everything
  • --surfaceCards, panels, menus
  • --surface-sunkenWells, code blocks, table stripes
  • --borderHairline dividers between surfaces
  • --border-strongInput and control outlines
  • --textBody copy and headings
  • --text-mutedCaptions, labels, timestamps
  • --accentPrimary button, links, focus ring
  • --accent-hoverAccent under the cursor
  • --accent-contrastLabel printed on the accent

Every pair that has to hold up

PairNeedsMeasuredCriterion
--text on --bg4.5:112.66:11.4.3 body copy
--text-muted on --bg4.5:14.56:11.4.3 body copy
--border-strong on --bg3:13.00:11.4.11 control boundary
--accent on --surface4.5:16.29:11.4.3 link text
--accent-contrast on --accent4.5:16.29:11.4.3 button label
--border on --surface1.33:1no requirement — decorative

The roles wearing their jobs

Invoice 0148

Issued 14 days ago · net 30

total = 1 284.00
Send reminderArchive

A link in the accent color inside a line of body copy.

Custom properties

:root {
  color-scheme: light;
  --bg: #fbfafe;
  --surface: #ffffff;
  --surface-sunken: #f3f1f7;
  --border: #e1dfe5;
  --border-strong: #939197;
  --text: #313032;
  --text-muted: #75727a;
  --accent: #4f46e5;
  --accent-hover: #3335d1;
  --accent-contrast: #ffffff;
}

How to generate a color scheme

One hex, two decisions, and a token block you can paste into a stylesheet.

  1. Give it the one color you are not allowed to change

    The brand hex goes in the field at the top; everything else in the scheme is derived from its hue. If the value came out of a logo file rather than a brand guide, the hue is what matters here and a point or two of saturation will not move the result. Switch to the dark variant with the toggle beside it — the accent is re-derived for that surface rather than reused, so the two modes will not hand you the same hex.

  2. Decide how much of the hue leaks into the greys

    The tint slider sets the chroma of the neutral roles between 0 and 8 in LCH. At zero you get true greys and the brand color appears only in the accent; at the top the backgrounds, surfaces and borders all carry a trace of the hue, which is what makes a grey look chosen rather than defaulted. Around 40% is where the tint is visible against white but not yet nameable as a color.

  3. Read the pair table, then take the block

    Six pairs are measured, each against the threshold that applies to it rather than against one blanket number, and the roles with an obligation were derived to meet it, so a failing row means the search hit a boundary and the input needs rethinking. The panel on the right shows the roles doing their jobs on a small card, and the copy button hands you a :root block of ten custom properties with color-scheme set to match.

Technical specifications

Tokens emittedTen — --bg, --surface, --surface-sunken, --border, --border-strong, --text, --text-muted, --accent, --accent-hover, --accent-contrast
Derivation spaceCIE LCH at the brand hue; neutral roles carry 0 to 8 chroma, set by the tint slider
Lightness laddersLight — surface 100, background 98.5, sunken 95.5, hairline border 89, body text 20. Dark — surface 16, background 11, sunken 7, hairline border 25, body text 96
Contrast search0.5 lightness per step, up to 200 steps, halting as soon as one clears the target
Thresholds enforced4.5:1 for body text, muted text, accent on surface and the accent label; 3:1 for --border-strong
Measured body contrast12.66:1 in the light variant, 15.17:1 in the dark variant
Hover stateThe accent moved 7 lightness points further from the surface it sits on
PriceFree, no signup, and no cap on how many schemes you derive

Frequently asked questions

Why ten tokens instead of a primary, a secondary and three greys?

Because a name that describes a job survives a redesign and a name that describes a rank does not. When the token is called --surface, every card in the codebase keeps working after you change what color a surface is; when it is called --secondary, nobody can tell whether a given usage meant a background, a border or a de-emphasised label, so the change has to be audited by hand. Ten is the smallest set that covers the distinctions an interface actually makes: two text weights, three depths of surface, two border weights, and three states of the accent.

What is the neutral tint slider actually changing?

The chroma of the grey roles, from 0 to 8, at the hue of your brand color in LCH. A true grey is chroma 0 and reads as an absence of decision; 8 is still low enough that nobody would name the color, but the surface stops looking like a browser default and starts looking related to the accent sitting on it. Chroma is used rather than HSL saturation because saturation at high lightness barely does anything — a #fafafa tinted by saturation is still #fafafa after rounding.

Does --border have to reach 3:1?

No, and that is why there are two border tokens. Success criterion 1.4.11 asks for 3:1 on visual information required to identify a user-interface component and its state — the outline of a text input that has no other boundary, a toggle in its off position, a focus ring. A hairline between two stacked cards identifies nothing, carries no state and has no contrast requirement at all. Conflating the two is how design systems end up with hard grey rules everywhere, so --border is left decorative and --border-strong is the one derived to clear 3:1.

Why is the accent darker than the brand color I typed?

Because a mid-lightness brand hue almost never clears 4.5:1 against a white surface, and a link is text. The generator starts at your color and steps lightness half a point at a time in the direction that gains contrast, stopping the moment the pair passes, so what comes back is the nearest usable relative of what you asked for rather than a safe dark that has lost the brand. Keep the original for logos, illustration and any mark over 24 pixels; use --accent where the color has to be read.

Can one brand color drive both a light and a dark theme?

Yes, but not by inverting the light one, which is the mistake the toggle exists to avoid. Lightness in the dark variant is compressed into a narrower band — background at L 11 against surface at L 16, where the light variant spans 98.5 to 100 — because the eye's contrast sensitivity is not symmetric and an inverted ladder produces surfaces nobody can tell apart. The accent is searched again from scratch against the dark surface and usually lands several points lighter than the brand color rather than several points darker.

Why not simply use black text on a white background?

Pure #000 on pure #fff measures 21:1 and is more contrast than most reading benefits from. At that ratio the white bleeds into the letterforms for readers with astigmatism — the effect is called halation — and long passages become physically tiring. The light variant here lands body text at L 20 on a background at L 98.5, which measures 12.66:1: comfortably past the 7:1 that AAA asks for at 1.4.6, with the glare taken off.

What is deliberately missing from the output?

Status colors — success, warning, danger, info. They are not derivable from a brand hue, because their meaning is conventional rather than mathematical: green for success and red for danger is a cultural agreement, and picking them by rotating your brand hue produces an amber warning on one brand and a purple one on the next. Choose them separately, then check each against the same background roles this page gives you.

About turning one color into a system

At the level a build cares about, a color scheme is not a set of colors — it is a set of names with obligations attached. Something has to be the page, something has to be the card on the page, something has to be the text on the card, and each of those relationships carries a number: 4.5:1 for body copy under WCAG 2.1 success criterion 1.4.3, 3:1 for anything that identifies a control under 1.4.11, 7:1 if you are targeting AAA at 1.4.6. A generator that hands back five swatches has answered a question about color and left the question about structure entirely open, which is why so many schemes look right in the presentation and fall apart in the component library. Naming the roles first and deriving the values second inverts that.

The arithmetic happens in LCH rather than in HSL for a reason that shows up immediately once more than one hue is involved. HSL lightness is a geometric position in the RGB cube, not a measure of how bright something looks: yellow at 50% lightness and blue at 50% lightness differ by a factor of roughly five in luminance, so an HSL ramp gives you a “500” step that is nearly white for one brand and nearly black for another. Stepping L in LCH puts the rungs where the eye finds them, and it is what lets a fixed ladder — 98.5, 95.5, 89, 20 — mean the same thing whatever hue is threaded through it. The same reasoning drives the numbered scale on the Tailwind color generator, and the shades table lays the LCH ramp beside the naive HSL one so the bunching is visible.

Two things go wrong often enough to be worth naming. The first is the dark theme built by inversion: take the light ladder, flip every lightness, ship it. It produces a page at L 1.5 with a surface at L 0, which is two surfaces nobody can distinguish, and body text at pure white on pure black, which is the halation problem doubled. Dark interfaces need a compressed lightness range and slightly less chroma, and their accents need to be searched again rather than reused. The second is expecting one tool to do two jobs: this page will not invent a palette for you, and the palette generator is where the exploring belongs — come back here once you know which color is the brand. If you already have a set of pairs from an existing product and want them audited rather than derived, the WCAG contrast checker takes a whole list at once.

Where your brand color is resolved

Every number on this page is worked out by JavaScript running in the tab you are reading it in. Nothing you type, paste or open is uploaded, logged or kept, which is also why the tools carry on working after you disconnect from the network.

The custom-property block is assembled as a string in this tab and handed to the clipboard from there, so the scheme never becomes a request. Nothing about your brand color is retained after the tab closes.