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.

Dark theme switch
Light
Card titleSecondary textButton
Dark
Card titleSecondary textButton

Contrast check

LightDark
text/bg (≥ 4.5)16.78:1 pass16.23:1 pass
text/surface (≥ 4.5)15.63:1 pass14.96:1 pass
muted/bg (≥ 4.5)7.21:1 pass7.60:1 pass
on-primary/primary (≥ 4.5)5.16:1 pass7.61:1 pass
on-accent/accent (≥ 4.5)9.77:1 pass9.77:1 pass
primary/bg (≥ 3)5.01:1 pass6.80:1 pass
accent/bg (≥ 3)2.08:1 fail8.74:1 pass
:root { color-scheme: light dark; --color-bg: #f8fcff; --color-surface: #edf4fe; --color-border: #cbd9ec; --color-text: #121b28; --color-muted: #4b5666; --color-primary: #2563eb; --color-on-primary: #ffffff; --color-accent: #f59e0b; --color-on-accent: #000000; } @media (prefers-color-scheme: dark) { :root { --color-bg: #0d121a; --color-surface: #151b23; --color-border: #2c3645; --color-text: #e9eff7; --color-muted: #99a6b8; --color-primary: #589cff; --color-on-primary: #000000; --color-accent: #f59e0b; --color-on-accent: #000000; } }

Runs in your browser: nothing you enter is sent anywhere.

How to use it

  1. Enter your primary and accent colors and a neutral that sets the tint of surfaces. Hex, rgb(), hsl(), oklch() and CSS names work.
  2. Choose how the dark theme is switched on.
  3. Read the contrast table. Fix any row that says fail by changing the primary or accent.
  4. 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.

More in Developer

All tools in this category: Developer ·Updated November 3, 2026

Privacy choices

Google Analytics shows us which tools are used and whether they work. It may set cookies. Your choice is kept in this browser.