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.
: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.
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.
| Token | Default | What 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:
:root {
--b-color-danger: light-dark(#c8005a, #ff4d94);
--b-color-danger-soft: light-dark(rgba(200, 0, 90, .12), rgba(255, 77, 148, .18));
}
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.
.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.