Skip to main content
Introducing packages.sweber.dev
Documentation menuWhat the steps promise

What the steps promise

Contrast targets of each step and the color pairs that are safe to use.

Every step is solved for a contrast ratio against the page: white in light mode, black in dark mode. The generated hex value reaches at least this ratio, and at most about 2 % more.

StepTargetTypical use
501.06:1Page and card background tint
1001.15:1Hover background, selected row
2001.32:1Active background, subtle border
3001.6:1Border, divider
4002.3:1Strong border, disabled text
5003.3:1Icons, input borders, focus ring
6004.9:1Text, links, solid buttons
7006.4:1Text on tinted surfaces, button hover
8008.5:1Small text, AAA
90012:1Headings
95015.5:1Highest contrast text

Safe pairs

These pairs pass in light and dark mode, for every input color. checkPalette() and gradient --check measure all of them.

ForegroundBackgroundRatioWCAG
500page, 50≥ 3:11.4.11 non-text contrast
600page, 50≥ 4.5:11.4.3 AA text
70050, 100, 200≥ 4.5:11.4.3 AA text
800page, 50≥ 7:11.4.6 AAA text
on-600 … on-950600 … 950≥ 4.5:1text on solid backgrounds

Steps of different scales mix too: contrast depends only on luminance, and luminance is the same per step for every hue. text-neutral-700 on bg-brand-100 passes like text-brand-700 does.

Text on a solid color

on-<step> is white or the deepest step of the scale (950 in light mode, 50 in dark mode), whichever has more contrast. Use it for buttons and badges:

<button class="bg-brand-600 text-brand-on-600 hover:bg-brand-700">Save</button>

Your exact brand color

The scale contains a color close to your input on the step named anchor, with the same hue and chroma, but with lightness adjusted to the target of that step. To keep the exact input, pass pin: true (CLI --pin). The step then has the contrast of your input, and checkPalette() reports it if that breaks a promise:

npx @sweberdev/gradient "#ec2a20" --pin --check
# fail brand light: 600 on white = 4.27:1, needs 4.5:1