Skip to Content
CSS Custom Properties for Theming a Multi-Brand Site
Advanced
0 likes
Tutorials

CSS Custom Properties for Theming a Multi-Brand Site

Switch brands with a data attribute, and avoid the derived-token trap that freezes hover colours on the wrong value.

One component set, several brands. The way to do it is to keep brand values in custom properties and switch them with an attribute. There is one trap in it, and it is easy to hit.

Step 1: brand tokens and a component that reads them

:root {
  --brand: #034bff;
  --brand-contrast: #ffffff;
  --radius: 6px;
}

[data-brand="harbor"] { --brand: #0b6e4f; --radius: 2px; }
[data-brand="ember"]  { --brand: #b42318; --radius: 999px; }

.btn {
  background: var(--brand);
  color: var(--brand-contrast);
  border-radius: var(--radius);
  border: 0;
  padding: 8px 16px;
}
<div data-brand="harbor"><button class="btn">Buy</button></div>
<div data-brand="ember"><button class="btn">Buy</button></div>

Chrome 154 computed the background and corner radius of the three buttons (default, harbor, ember):

default  rgb(3, 75, 255)    6px
harbor   rgb(11, 110, 79)   2px
ember    rgb(180, 35, 24)   999px

No component rule changed. Only the attribute did.

Step 2: add a derived hover colour, and see the trap

:root {
  --brand: #034bff;
  --brand-hover: color-mix(in srgb, var(--brand) 85%, black);
}

.btn:hover { background: var(--brand-hover); }

This looks right and fails for the other brands. I hovered the default button and the harbor button:

hover default   color(srgb 0.01 0.25 0.85)
hover harbor    color(srgb 0.01 0.25 0.85)

Both are the same dark blue. The cause is that var(--brand) inside --brand-hover is resolved on :root, where --brand is still the default blue, and the finished value is then inherited. The harbor block changes --brand, but --brand-hover has already been computed.

Step 3: declare derived tokens where the brand changes

:root, [data-brand] {
  --brand-hover: color-mix(in srgb, var(--brand) 85%, black);
}

Now each brand element recomputes the hover colour from its own --brand. After the change:

hover default   color(srgb 0.01 0.25 0.85)
hover harbor    color(srgb 0.0366667 0.366667 0.263333)

Harbor's hover is now a darker green. The default is unchanged.

⚠ Warning: Any token built from another token has to be declared at, or below, the place that changes the source. Otherwise it freezes with the source's value from the parent.

Check each brand's contrast

White text on #0b6e4f is 6.25:1 and on #b42318 it is 6.57:1. Both pass 4.5:1. Check contrast every time a brand is added, because a pale brand colour with white text can fail.

Common mistakes

  • Declaring derived tokens only on :root.
  • Putting brand-specific rules in components (.btn--harbor) instead of tokens.
  • Letting a brand override a primitive token that other brands share.

Related course: HTML and CSS Advanced.

About

Switch brands with a data attribute, and avoid the derived-token trap that freezes hover colours on the wrong value.

Responsible
John G
Last Update
08/20/2026
Reading Time
3 min
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.