Skip to content
← ALL EXPERIMENTS

13 / CSS / 2026-04-11

Palette Scavenger

One hex in, an 11-step brand scale out.

How it works

// 01 DPI and pixel density

The input hex is parsed to RGB, then converted to OKLCH (L, C, H) using the standard conversion matrix. OKLCH is a perceptually uniform color space where L is lightness (0 to 1), C is chroma (saturation, roughly 0 to 0.4), and H is hue (0 to 360 degrees). The input color's L, C, and H values are extracted and used as the anchor for step 500.

// 02 Bleed and cut tolerance

The other ten steps are generated by sweeping L through a fixed ladder (roughly 0.98, 0.95, 0.89, 0.83, 0.74, anchor, 0.57, 0.48, 0.38, 0.26, 0.16) while keeping hue constant and gently pulling chroma toward zero at the extremes. Chroma compression at the ends prevents the lightest tint and darkest shade from looking radioactive. The anchor's chroma is preserved for the mid-steps so the identity of the color stays legible.

// 03 Safe zone and trim accuracy

Each generated OKLCH value is converted back to RGB, clipped to the sRGB gamut if necessary, and expressed as a hex value. Contrast ratios are computed using the WCAG formula (relative luminance ratio with a plus-0.05 constant) against both pure white and pure black, so you know at a glance which steps are safe for foreground text.

OBJECT / palette.scaleCSS
......
......

// why this exists

design systems

Brand palettes usually start with a single hex value. The designer has a favorite. The founder has a favorite. The logo has a favorite. What you need for a working design system is eleven values, not one: a tonal scale ranging from a near-white tint to a near-black shade, with enough contrast between steps that the system can express hierarchy without reaching for a second hue. Tailwind popularized the 50 / 100 / 200 / 300 / 400 / 500 / 600 / 700 / 800 / 900 / 950 convention and this widget produces exactly that from any single hex input.

Paste a hex value. The widget anchors it at the 500 step (the middle of the scale) and derives the other ten steps by walking lightness up and down in perceptually uniform space. The result is a palette where each step feels like the same amount of change as the one before it. That consistency is what lets a design system scale across buttons, backgrounds, borders, and type without the palette looking off-kilter.

The derivation happens in OKLCH color space, not HSL. HSL is the easy choice and it is wrong. In HSL, 50% lightness yellow and 50% lightness blue look nothing alike in perceptual brightness, which means an HSL-derived scale produces uneven contrast steps. OKLCH is a perceptually uniform space designed to fix exactly that. A 10% change in OKLCH lightness looks like a 10% change to the human eye, regardless of hue. Every major design system (Tailwind v4, Radix, Adobe Spectrum) has moved to OKLCH for this reason.

Copy any step as a hex value, as an OKLCH value, as a CSS custom property, or as a full Tailwind config block. The full config snippet drops directly into `tailwind.config.ts` under `theme.extend.colors`, which means you can tune a palette here and ship it to your codebase in about thirty seconds.

The widget also shows contrast ratios for each step against white and black. That lets you see at a glance which steps pass WCAG AA for text (4.5:1 for body, 3:1 for large text) and which are for backgrounds only.

Frequently asked questions

Why OKLCH instead of HSL?

HSL is not perceptually uniform. 50% lightness yellow and 50% lightness blue look like vastly different brightnesses to the eye. OKLCH is designed so equal lightness values produce equal perceptual brightness, which is what you need for a consistent tonal scale.

What does the 50 to 950 scale mean?

It is the Tailwind convention. 50 is the lightest tint (near white), 500 is the anchor color, 950 is the darkest shade (near black). Each step is roughly equivalent to one level of lightness change on a perceptually uniform axis.

How do I know a step passes WCAG AA for text?

The widget shows contrast ratios against white and black for each step. 4.5:1 or higher is AA compliant for body text. 3:1 or higher is AA compliant for large text and UI components.

Can I anchor my color at something other than 500?

Not in this version. The anchor is always step 500 because that is the Tailwind convention and the mental model most designers expect. If your brand color is really a 400 or 600, you can generate, then shift the values.

Why does my brand color look different at the extremes?

sRGB cannot represent every OKLCH combination, so colors at the edges of the gamut get clipped when converted back to hex. That is a limitation of the display standard, not the widget.

What is chroma compression at the ends?

At very light and very dark lightness values, high chroma looks unnatural. The widget gently pulls chroma toward zero at the extremes to keep tints and shades looking like the same color family, not like separate hues.

Can I export directly to Tailwind config?

Yes. Copy the 'Tailwind' preset and paste it into theme.extend.colors in your tailwind.config.ts. It is a drop-in palette.

What is relative luminance?

A weighted average of the R, G, B channels that approximates how bright a color appears to the human eye. It is the foundation of the WCAG contrast ratio formula.

Why 11 steps and not 10 or 12?

10 steps (50 through 950 skipping one slot) plus 950 gives 11. Tailwind's original 10-step palette proved not dark enough for dark mode UIs, so 950 was added in 2023. That is now the accepted system.