Define each color once, use it everywhere, change it in one place
How to recolor a website with CSS variables
To change a website's colors with CSS variables, define each color once as a custom property on :root, use var() wherever that color appears, and change the property's value to repaint every use at once. Recolor (recolor.page) does this to any page you load: it rewrites every color on the page into a variable, after which a whole new palette is a single write.
Last updated
| Step | What you write | Why |
|---|---|---|
| 1. Collect | A list of every color the CSS uses | Hover states, borders, shadows and gradients hide colors too |
| 2. Define | :root { --color-primary: #2a9d8f; } | One place per color |
| 3. Use | background: var(--color-primary); | Every use follows the definition |
| 4. Swap | A new value, by class or with setProperty | The whole site repaints at once |
| 5. Check | Text against its background | Lightness decides whether it stays readable |
The idea in a few lines of CSS
A custom property holds a value; var() reads it. Put the palette on :root and point the rules at it:
:root {
--color-primary: #2a9d8f;
--color-surface: #ffffff;
--color-text: #264653;
}
.button {
background: var(--color-primary);
color: var(--color-surface);
}
body {
background: var(--color-surface);
color: var(--color-text);
}A second palette is a second set of definitions, switched by an attribute, a class or a media query such as
prefers-color-scheme. Nothing else in the stylesheet changes:
/* A second palette: only the definitions change. */
[data-palette="sunset"] {
--color-primary: #e76f51;
--color-text: #3d1d00;
}Finding every color first
The work is in step 1. Colors sit in more places than color and background-color: border shorthands,
box-shadow and text-shadow, every stop of a gradient, outlines, SVG fill and stroke, inline styles in
the HTML, and transparent versions such as rgb(0 0 0 / 0.5). A hover color left as a literal hex keeps the old
palette the moment someone points at the button. Recolor's engine reads all of these, which is why it can repaint
sites that never used variables; How Recolor repaints a page lists what it covers.
Name colors by role, and derive the shades
Name variables by what they do, --color-primary or --color-surface, not by what they are, --teal: the
name then survives the next palette. Tints, shades and transparent versions do not each need a variable; modern
CSS can derive them from one:
.badge {
/* A light tint of the primary color, without a variable of its own. */
background: color-mix(in oklch, var(--color-primary) 15%, white);
}
.overlay {
/* The same color at half opacity, with relative color syntax. */
background: rgb(from var(--color-primary) r g b / 50%);
}Recolor uses the second form itself: on pages built with Tailwind 3, colors whose opacity comes from a variable are rewritten into relative colors, so they keep their opacity when the palette changes. If your palette ends up in Tailwind, Tailwind v4 @theme colors from a palette shows the same idea there.
Swapping from JavaScript
Scripts can change a variable on any element; on :root it reaches the whole page:
// One write repaints every element that uses the variable.
document.documentElement.style.setProperty("--color-primary", "#e76f51");For many colors at once it is cheaper to replace the text of one <style> element that defines them all, so the
browser restyles once. That is what Recolor does on every press of Space. On October 7, 2026 the
production build wrote the new variables 3 ms after the key on its landing sample, and on
github.com, with 28,104 declarations, the browser repainted
1 to 3 frames after the palette strip.
How Recolor adds variables to a site that has none
When a page loads, the engine walks every stylesheet it can read, the inline styles and the color attributes of SVG
and old HTML. Each exact color gets its own variable, named like --rc-c3m1, and every declaration that used the
color is rewritten to var(--rc-c3m1), keeping its !important and its place in the cascade. The variables start
at the original values, so the page looks the same. Then the palette only ever touches the variables. Which color
each variable gets is the mapping, explained on Strength, neutral tint and overrides.
What variables cannot recolor
Pixels. Photos, PNG logos, video, canvas drawings and WebGL keep their colors whatever the CSS says, which is easy to
see by previewing a palette on the real website; an SVG file
loaded through an <img> does too, while SVG written into the HTML follows CSS. Plan for that in a redesign:
a logo or an illustration may need a new file. And a new value is not automatically a readable one: keep text and
its background apart in lightness, as keeping a palette accessible
explains.
Common questions
Do CSS variables work in every current browser?
Yes. Custom properties and var() work in all current browsers; color-mix() and relative colors need recent versions, so check them for older targets.
Can I change a CSS variable from JavaScript?
Yes. element.style.setProperty("--name", value) changes it on that element and everything inside it; on document.documentElement that is the whole page.
Do I need a variable for every shade of a color?
No. color-mix() and relative color syntax derive tints and transparent versions from one variable.
Will changing the variables recolor images and logos?
No. Images, video and canvas are pixels; only inline SVG follows CSS colors.
Can I try a new palette on my site before rewriting its CSS?
Yes. Load the site into Recolor: it adds the variables itself, so you can press Space through palettes on the real page first.
Can Recolor give me the variables for a palette?
Yes. Its Export panel writes CSS variables, a Tailwind v4 @theme block, SCSS and JSON.
Sources
- Using CSS custom properties, MDN developer.mozilla.org
- var(), MDN developer.mozilla.org
- CSSStyleDeclaration: setProperty(), MDN developer.mozilla.org
- color-mix(), MDN developer.mozilla.org
- Using relative colors, MDN developer.mozilla.org