Skip to content
Barua UI v0.2
Foundations

Theming

Light and dark are already handled — every colour is a light-dark() pair, so the system follows the operating system with nothing switched on. This page is about the other job: making the system carry your colour without breaking the meanings the rest of it depends on.

The named accents

Five accents ship with the system. Set one attribute on <html> and every button, focus ring, link and selected state re-tints.

data-accent works at any depth, not only on the root, so a single view can carry its own accent when it genuinely means something — a workspace colour, a tenant, a status board.

A brand colour that is not on the list

A brand rarely matches a palette. Override the accent tokens in unlayered CSS — a plain stylesheet loaded after barua.css. Unlayered rules beat every layer in the system, which is the documented seam for exactly this. It is the supported route, not a workaround.

brand.css — loaded after barua.css
:root {
  --b-color-accent:        light-dark(#f43308, #ff5a33);
  --b-color-accent-hover:  light-dark(#d92c06, #ff7a5c);
  --b-color-accent-active: light-dark(#b02404, #ff9880);
  --b-color-accent-soft:   light-dark(rgba(244, 51, 8, .12), rgba(255, 90, 51, .18));
  --b-color-accent-text:   light-dark(#c62905, #ff8566);
}

Five tokens, and always as a light-dark() pair. A colour chosen against white is usually too dark on black — the dark value is typically the same hue lifted in lightness and dropped in saturation. Give both, or dark mode inherits a brand colour nobody checked.

Live

Nothing above was restyled. The focus ring, the tinted button, the badge and the accent shadow are all color-mix() over --b-color-accent, so they follow on their own.

Check what your colour now collides with

This is the part that bites. Two system colours are defined in terms of the accent or sit close to it, and a brand colour can quietly erase a distinction the interface relies on.

TokenDefaultWhat breaks
--b-color-info var(--b-color-accent) Follows your brand by design. Usually right — informational and accent are the same voice.
--b-color-danger var(--b-color-red) A warm accent kills it. With a red or orange brand, a destructive button and a primary button read as the same thing, and a breach notice reads as a link.
--b-color-warning orange Collides with an orange or amber brand for the same reason.

When your accent lands in the red-to-amber arc, move danger rather than accepting the collision. Crimson keeps the alarm and separates cleanly from an orange brand:

brand.css, continued
:root {
  --b-color-danger:      light-dark(#c8005a, #ff4d94);
  --b-color-danger-soft: light-dark(rgba(200, 0, 90, .12), rgba(255, 77, 148, .18));
}
The test. Put the primary button and the danger button side by side, then look at them in both themes. If you have to read the label to tell which one destroys something, the colours are wrong however good each looks alone.

What not to override

Accent tokens are yours. The greyscale is not: --b-text, --b-text-secondary, --b-surface, --b-separator and the fills carry the contrast contract that every component is built against, in both themes at once. Change those and you are not theming the system, you are replacing its foundation — and the accessible contrast that came free stops being guaranteed.

If a surface needs to be a different colour in one place, reach for the material tokens or a scoped accent instead of moving the global greyscale.

Scope it when the colour means something

Overriding on :root themes the product. Putting the same tokens on a class themes a region — useful for a tenant, an environment banner, or one board among many. The tokens cascade, so anything inside picks them up.

Scoped instead of global
.tenant-agentic {
  --b-color-accent:       light-dark(#f43308, #ff5a33);
  --b-color-accent-hover: light-dark(#d92c06, #ff7a5c);
}

In React, nothing changes: the tokens are CSS, so a className or a style holding custom properties themes any subtree.