Skip to content
HexSlate

Color Shades & Tints Generator

Steps come out as brand-500.

50
#f5f8ff
AA black 19.8
oklch(0.9789 0.0099 262.87) clamped
100
#e9f0ff
AA black 18.4
oklch(0.9547 0.0214 262.88) clamped
200
#d3e2ff
AA black 16.1
oklch(0.9114 0.0425 262.88) clamped
300
#b4ceff
AA black 13.2
oklch(0.8491 0.0743 262.88) clamped
400
#7da9ff
AA black 9.0
oklch(0.7394 0.134 262.88) clamped
500
#4a85ff
AA black 6.1
oklch(0.6397 0.1925 262.88) clamped
600Base
#2563eb
AA white 5.2
oklch(0.5461 0.2152 262.88)
700
#194ec2
AA white 7.2
oklch(0.4676 0.1901 262.88)
800
#0f3a9a
AA white 10.0
oklch(0.389 0.164 262.88)
900
#082d80
AA white 12.4
oklch(0.3353 0.1456 262.88)
950
#011650
AA white 17.0
oklch(0.2338 0.1092 262.88)
Some steps ran out of chroma The steps marked "clamped" asked for more chroma than sRGB has at that lightness, so it was lowered until they fit, holding the lightness and the hue exactly. Near white and near black the gamut comes to a point, so this is ordinary for a saturated hue rather than a fault. The hex on each swatch is the colour a browser will paint.
Stepped in OKLCH against a mix in sRGB hue drift 0.0° against 3.3°
OKLCH
sRGB mix
50 100 200 300 400 500 600 700 800 900 950

Read down the columns. The sRGB row loses chroma toward both ends and its dark steps land far darker than the ladder asks for, which is why hand-rolled scales run out of colour before they run out of steps.

Export
theme.css
@theme {
  --color-brand-50: oklch(0.9789 0.0099 262.87);
  --color-brand-100: oklch(0.9547 0.0214 262.88);
  --color-brand-200: oklch(0.9114 0.0425 262.88);
  --color-brand-300: oklch(0.8491 0.0743 262.88);
  --color-brand-400: oklch(0.7394 0.134 262.88);
  --color-brand-500: oklch(0.6397 0.1925 262.88);
  --color-brand-600: oklch(0.5461 0.2152 262.88);
  --color-brand-700: oklch(0.4676 0.1901 262.88);
  --color-brand-800: oklch(0.389 0.164 262.88);
  --color-brand-900: oklch(0.3353 0.1456 262.88);
  --color-brand-950: oklch(0.2338 0.1092 262.88);
}
base #2563ebOKLCH L 0.546base step 600hue drift 0.0° / sRGB 3.3°6 of 11 clamped into sRGB

Eleven rungs from 50 to 950, stepped in OKLCH so each one lands where your eye puts it rather than where sRGB arithmetic does. Your color is reproduced exactly on the rung its own lightness belongs at, which is often not the 500. Everything runs in your browser, and the base color, the variable name and both settings ride in the query string, so a link reproduces this scale exactly.

A tonal scale is a promise about lightness, and HSL cannot keep it

When a design system offers blue-500 and amber-500, it is promising the two carry the same visual weight, so a badge built on one reads like the same badge built on the other. That promise is about perceived lightness, which HSL does not measure: its L is the midpoint of the largest and smallest sRGB channel. hsl(60 80% 50%) comes back at OKLCH lightness 0.894 and hsl(264 80% 50%) at 0.491, both claiming the same nominal 50%. Even steps in HSL are not even either. Walking hsl(264 80% L) down from 95% in tens gives 0.944, 0.831, 0.720, 0.615, 0.525, 0.455, 0.381: the first drop is worth 0.113 of perceived lightness and the drop from 55% to 45% only 0.070. OKLab, which OKLCH is the polar form of, was fitted to perceptual data precisely so that equal numeric steps look equal, and the color format converter will read any color's OKLCH lightness back to you.

What 50 to 950 is for, and where the numbers came from

The 50 end is surfaces and the 950 end is ink. In practice 50 and 100 are page and card backgrounds, 200 and 300 are borders and dividers, 400 to 600 is where a filled button or a link lives, and 700 upward is text, hover states and dark surfaces. The numbering stops short of 0 and 1000 because both ends have to stay distinguishable from white and black rather than reach them.

The lightness of each rung is not invented here. Each one is the mean OKLCH lightness of Tailwind v4's 25 built-in ramps at that step, taken from tailwindcss/theme.css: 0.979 at 50, 0.642 at 500, 0.235 at 950. A generated brand-500 therefore reads at the same weight as the built-in blue-500 beside it in a mock-up. Notice how uneven the ladder is, with 0.024 between 50 and 100 against 0.102 between 900 and 950. That is deliberate: the light end is a set of near-white surfaces that have to stay apart from each other, and the dark end has to stay apart from black.

Chroma cannot be held constant, and pretending otherwise shows

"Constant chroma" describes the method correctly everywhere except the two ends, where it fails outright. The sRGB gamut is not a cylinder. It narrows to a point at white and at black, neither of which has any chroma at all, so asking for your base's chroma at lightness 0.979 asks for a color that does not exist. This tool tapers chroma toward both ends on a curve fitted to Tailwind's own mean chroma per step, and gives it up faster on the light side than the dark side, because a 50 should be a hint of color while a 900 is a text color that reads dead once the hue is bleached out of it. Whatever still falls outside sRGB is mapped the way CSS Color 4 describes: lightness and hue held, chroma reduced until the color fits, never by clipping the three channels separately, which moves them by different amounts and therefore shifts the hue. Those steps are marked, and the status bar counts them. Turn the taper off to see what constant chroma really produces at the ends.

Constant hue is the part you can see

The strip under the scale is your base run twice: once stepped in OKLCH, once mixed toward white and black in sRGB, anchored on the same step so the comparison is fair. The sRGB method is not a straw man. It is what lighten() and darken() did in Sass and what most "tint and shade" snippets still do, and it fails in two ways that are both on screen. The hue moves: start from oklch(0.55 0.15 57), a mid brown, and its darkest sRGB step comes back 33 degrees round the hue wheel from where it started. Sweep all 360 hues at that lightness and chroma and the OKLCH scale stays inside 0.013 degrees of the base on every one of them. And the chroma drains: #2563eb mixed 90% toward black lands at #040a18, which is lightness 0.145 where the ladder asks for 0.235 and chroma 0.032 against 0.109. The dark end of an sRGB mix is not a deep version of your color, it is a near black with a trace of it left in.

Common problems

  • The 500 is not my brand color. It will not be, unless your color already sits near lightness 0.642. The tool marks the step your color's own lightness puts it at and reproduces it there exactly, rather than quietly handing back a different color under the label you asked for.
  • The scale does not match Tailwind's canonical lightness. The ladder is stretched so that your color lands exactly on its own step, and the further your color sits from that rung, the further the whole ramp drifts with it. The drift is bounded by half a step. Nudging your base toward a rung tightens the whole scale.
  • Every step looks like the same grey. Your base has almost no chroma, and there is no hue in a grey to carry down a ramp. That is a perfectly good neutral scale, just not a colored one. Raise the chroma of the base if you wanted color.
  • The Tailwind block does nothing.@theme is v4 syntax and the block goes straight under your @import "tailwindcss"; line. A v3 project ignores it entirely and wants the values in theme.extend.colors in tailwind.config.js. Take the JSON export for that.
  • The variable name is not the one you typed. The field is slugified, so Acme Blue becomes acme-blue, and a name starting with a digit gets a letter put in front of it, because $500-50 is a Sass parse error rather than a warning.

Frequently asked questions

Why did my color land on 600 instead of 500?

Because 600 is where its lightness puts it. The page opens on #2563eb, the blue Tailwind shipped as blue-600 through v3: its OKLCH lightness is 0.546 and the ladder's 600 rung sits at 0.549, so calling it a 500 would be wrong by a full step. Most brand colors are chosen to work as a button or a link, which puts them in the 500 to 700 band rather than at any particular number.

Should I export hex or OKLCH?

OKLCH for anything you will edit later, hex for anything that has to run in a browser older than May 2023. Every value here is inside sRGB, so the two notations name the same pixel. The difference is editing: nudging the first number in oklch(0.6397 0.1925 262.88) changes the lightness and nothing else, while nudging a byte in a hex value changes lightness, chroma and hue at once. oklch() has shipped in Chrome 111, Safari 15.4 and Firefox 113 and is what Tailwind's own palette is written in.

Which text color can I put on each step?

Every swatch carries a badge naming the ink that clears WCAG 2.1 AA on it, at 4.5:1 for normal text, and the ratio it clears by. One of black or white always passes, because the two ratios multiply to exactly 21 for any color and 4.5 by 4.5 is 20.25. So the badge is not a pass or a fail, it tells you which of the two you are committed to. Contrast is symmetric, so a step whose badge reads black is also a step you cannot use for body text on a white page. The contrast checker works the same numbers against a specific pair.

Can I build a neutral grey ramp with this?

Yes, and it is worth doing deliberately. Feed it a near-grey with a trace of your brand hue, around 0.01 to 0.02 chroma, rather than a true grey. The taper scales that trace with the chroma the gamut actually has at each lightness, so a hint of the hue is still there at 950 instead of draining out the way an sRGB mix drains it, and a neutral carrying a trace of the brand hue is what stops an interface looking like a wireframe.

What can this tool not do?

It produces a starting point, not a finished brand palette. It knows one color and the arithmetic of lightness; it does not know that your 100 has to sit under a specific photograph, that legal signed off on one exact hex for the logo, or that your 700 is also the disabled state and needs to lose against the 600 at a glance. Real systems hand-tune two or three steps after generating the rest, and Tailwind's own ramps are hand-tuned enough that no single formula reproduces their 300 and 400. Generate, then check the ends against the surfaces they will actually sit on. Pair it with the palette generator when you need the other colors that go with this one.