CSS variables theme generator
Enter a primary color, an accent and a neutral, and get background, surface, border, text and brand variables for a light and a dark theme. A contrast table shows which pairs reach WCAG AA.
Runs in your browser: nothing you enter is sent anywhere.
How to use it
- Enter your primary and accent colors and a neutral that sets the tint of surfaces. Hex, rgb(), hsl(), oklch() and CSS names work.
- Choose how the dark theme is switched on.
- Read the contrast table. Fix any row that says fail by changing the primary or accent.
- Copy the CSS into your stylesheet.
How it works
The neutral’s hue is combined with fixed OKLCH lightness values to build the background, surface, border, text and muted text colors for each theme. Light backgrounds sit near lightness 99 and text near 22; dark backgrounds near 18 and text near 95. In the contrast table the ratio is (L1 + 0.05) ÷ (L2 + 0.05), where L1 and L2 are the relative luminances of the lighter and darker color.
Example
Primary #2563eb with a white label has a ratio of about 5.2:1, which passes 4.5:1, so the light theme uses white text on the primary button. In the dark theme the primary is lifted to a lighter blue, and the label switches to black if that gives a higher ratio.
Tips
- Keep the variable names semantic (text, surface) rather than literal (blue), so you can restyle a theme without renaming.
- Check any extra colors you add yourself with the contrast checker.
Frequently asked questions
How are the light and dark colors chosen?
Backgrounds, borders and text are grays tinted with the hue of your neutral color, set at fixed lightness steps in the OKLCH color space. These steps are a design choice, not a standard. Your primary and accent keep their hue and are lightened in the dark theme if they are darker than OKLCH lightness 72.
What does the contrast check measure?
It uses the WCAG 2 contrast ratio. Text pairs should reach 4.5:1 for normal text. Brand colors against the background are checked against 3:1, the usual minimum for large text and interface parts such as borders and icons.
Why is the text on my primary color black or white?
The tool picks whichever of the two has the higher contrast with the color. If both are below 4.5:1 the table shows a failure, and you should choose a lighter or darker primary.
Which dark theme switch should I use?
System setting follows the visitor's prefers-color-scheme. data-theme lets your own toggle set the theme on the html element. Both uses the system setting unless data-theme is set to light, and a data-theme of dark forces dark.
How do I use the variables?
Reference them with var(), for example background: var(--color-bg) and color: var(--color-text). Change the prefix field to rename them all.