How to Switch a Website’s Theme Color Using CSS Variables

TIPS

If a website needs to change its primary buttons and information panels from blue to green, editing every CSS declaration separately is inefficient. CSS (Cascading Style Sheets, the rules that control how a web page looks) lets you centralize colors with reusable custom properties, often called CSS variables. This guide shows how to switch a website’s brand colors with a button, without changing its overall light or dark appearance.

Define theme colors by their roles with CSS variables

A CSS custom property has a name beginning with two hyphens, such as --brand. Other declarations refer to its value with var(--brand). The :root selector represents the document’s top-level element; ordinary custom properties defined there can be inherited by elements further down the page. Keeping a button’s background and text colors in separate variables makes their relationship easier to maintain.

Start with a blue palette: #1D4ED8 for the primary button, #1E40AF when the pointer hovers over it, and #FFFFFF for the button text. For an information panel, use the pale blue background #EFF6FF with the darker blue text #1E3A8A. These are HEX codes (six hexadecimal digits preceded by #). The equivalent colors in the CSS example are written as RGB values, which specify their red, green, and blue components as numbers.

:root {
  --brand: rgb(29 78 216);
  --brand-hover: rgb(30 64 175);
  --on-brand: rgb(255 255 255);
  --brand-soft: rgb(239 246 255);
  --on-brand-soft: rgb(30 58 138);
}

Here, --brand is the primary color, --brand-hover is its hover color, and --on-brand is the text placed on that background. The --brand-soft and --on-brand-soft pair controls the information panel. Naming values by their purposes rather than calling them “blue” means the components can use the same variables when the palette changes to green.

Switch from blue to green with a data attribute and button

The green palette uses #166534 for the primary button, #14532D for its hover state, #F0FDF4 for the information panel, and #14532D for the panel text. The button’s text remains #FFFFFF in both palettes.

Use :root[data-brand="green"] to override the appropriate custom properties. A data-* attribute is an extra HTML attribute that can store a value for CSS selectors and scripts to use. Components still refer only to their variables through var():

:root[data-brand="green"] {
  --brand: rgb(22 101 52);
  --brand-hover: rgb(20 83 45);
  --brand-soft: rgb(240 253 244);
  --on-brand-soft: rgb(20 83 45);
}
.primary-button {
  background-color: var(--brand);
  color: var(--on-brand);
}
.primary-button:hover {
  background-color: var(--brand-hover);
}
.notice {
  background-color: var(--brand-soft);
  color: var(--on-brand-soft);
}

Add a button to trigger the change, plus components that use the two color roles. Setting type="button" prevents this control from submitting a form.

<button type="button" id="brand-switch">Switch to green</button>
<button type="button" class="primary-button">Contact us</button>
<p class="notice">Find our latest updates here.</p>

CSS supplies the two palettes, but JavaScript (code that responds to actions such as clicks) is needed for this button-driven switch. Run the following script after the HTML buttons have been loaded:

const button = document.querySelector("#brand-switch");
button.addEventListener("click", () => {
  const root = document.documentElement;
  const next = root.dataset.brand === "green" ? "blue" : "green";
  root.dataset.brand = next;
  button.textContent = next === "green" ? "Switch to blue" : "Switch to green";
});

Initially, the blue values from :root apply. The first click sets data-brand to green on the document’s root element, so the green overrides take effect. The next click sets it to blue, which no longer matches the green selector and restores the base values. The control’s label also names the next available color, so its meaning does not depend on color alone.

Check contrast and interaction states after switching

Always check the actual text-and-background pair in each palette. White #FFFFFF text on the blue #1D4ED8 primary button has a contrast ratio of approximately 6.70:1; on green #166534, it is about 7.13:1. With the hover colors, the ratios are about 8.72:1 on blue #1E40AF and 9.11:1 on green #14532D.

For the information panels, dark blue #1E3A8A on pale blue #EFF6FF gives approximately 9.52:1; dark green #14532D on pale green #F0FDF4 gives about 8.70:1. These combinations exceed the 4.5:1 contrast threshold for normal-size text under WCAG (Web Content Accessibility Guidelines). This does not guarantee the same result when text is placed against a different background or image.

On a real site, test links, mobile fixed buttons, hover states, and visible keyboard focus indicators in both palettes. Also make the active choice or available action understandable in text rather than conveying it by color alone. This example returns to the default blue palette after a page reload; keeping the user’s selection requires additional code to save and restore that preference.