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

From the CSS specifications as described on MDN, and Recolor's engine, checked October 8, 2026
StepWhat you writeWhy
1. CollectA list of every color the CSS usesHover states, borders, shadows and gradients hide colors too
2. Define:root { --color-primary: #2a9d8f; }One place per color
3. Usebackground: var(--color-primary);Every use follows the definition
4. SwapA new value, by class or with setPropertyThe whole site repaints at once
5. CheckText against its backgroundLightness 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:

CSS
: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:

CSS
/* 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:

CSS
.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:

JavaScript
// 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