Theming
Every design value in Lovelace flows through CSS custom properties named --lovelace-*. tokens.css defines them. Component styles only reference them through var(). To change how the components look, you re-point tokens; you do not fight component CSS.
To experiment before you read further, jump to the theme playground.
The three token tiers
Tokens form a chain of three tiers. Each tier references the one below it.
For example, the primary button background resolves as:
Pick your tier by blast radius. Override a semantic token to restyle every surface that shares the role. Override a component token to change one surface only. Primitive tokens are not overridable through the provider; they exist to feed the tiers above.
Override tokens per brand
Pass the override prop to LovelaceProvider. Keys are the overridable semantic and component tokens, typed as LovelaceOverridableToken. Values are CSS values.
Token keys drop the --lovelace- prefix. The provider applies each override as an inline CSS variable on its root element, and the value cascades to every Lovelace component inside.
TypeScript rejects unknown keys and primitive-token keys at compile time.
The tokens module
@ada-cx/lovelace/tokens exports typed helpers for working with tokens:
t(name)returns avar()reference, for examplet("font-size-14")returns"var(--lovelace-font-size-14)". Use it to point one token at another.tokenName(name)returns the bare custom property name, for example"--lovelace-font-size-14".LovelaceToken,LovelaceOverridableToken, andTokenRefare the token name and reference types.
How Ada applies your brand settings
The Ada widget does not ship per-brand CSS. It maps the brand settings from the Ada dashboard, under Settings > Chat window, to a token override map, and passes that map to LovelaceProvider. The mapping is a public export named createBrandOverrides. A custom UI that calls it applies your brand exactly the way the widget does.
The second argument is the resolved color scheme, not the configured theme. Resolve "auto" through useResolvedTheme first (see Light and dark themes). The mapping decides whether your accent color can serve as text, and that decision depends on the surface the text lands on.
The settings object
Every BrandSettings field is optional. An absent or null field contributes no overrides, so createBrandOverrides({}, scheme) returns {}.
The types BrandCornerStyle, BrandOverrides, BrandSettings, and BrandTextSize ship with the package.
The mapping
createBrandOverrides applies these rules, in order:
cornerStyle: "rectangular"re-points the message, composer, media preview, and container radius tokens at the flatborder-radius-10primitive.textSizeshifts the caption, body, and title font-size and line-height tokens one step down ("small") or up ("large"). The defaults contribute nothing.tintColorpaintscolor-background-accentunconditionally. Text on the accent fill,color-content-on-accent, becomes black or white, whichever contrasts more with the tint.- The tint also becomes standalone accent text,
color-content-accent, but only when it reads against the rendered scheme’s surface. A tint close to the background would make labels invisible, so the tint must clear a 3:1 contrast floor, or at least read no worse than the default value it would replace. - With
advancedColorsEnabled,textOverAccentColorreplaces the derived on-accent text color verbatim. - With
headerColorEnabled,headerColorpaintsheader-background. The header text isheaderTextColor(advanced colors only), repaired to black or white when it measurably falls below the WCAG AA 4.5:1 contrast floor against the header background. Without a configured header text color, the mapping derives the maximum-contrast black or white from the background.
The legibility guards measure hex colors only. Values the parser cannot read, such as named colors or rgba() values, pass through unrepaired where they are kept at all. Repairing an unmeasurable value would trade one unknown rendering for another.
Colorimetry helpers
The contrast measurements behind the mapping are public too:
contrastRatioBetween(a, b)returns the WCAG contrast ratio between two hex colors, ornullwhen either cannot be parsed.isLegibleAgainst(color, surface, fallback)reports whether a color is safe to use as accent text on a given surface.isLegibleOnSurface(color, scheme)runs the same check against the default Lovelace surface for a scheme.LOVELACE_SURFACESholds the default light and dark surface colors the checks measure against.readableTextOnAccent(accent, fallback)picks black or white for text painted on an accent fill.
Theme playground
Try your brand on real Lovelace components. The playground runs createBrandOverrides, the same function the Ada widget uses, so the preview matches what the widget renders.
Six presets seed the controls: Ada, Ocean, Sunset, Terminal, Bubblegum, and Mono noir. Each preset is a complete combination of accent, theme, corner style, text size, and header colors. Changing any control clears the active preset and keeps your values.
The interactive theme playground could not load in this view. The sections above describe the same token system and the same brand mapping.
Every control corresponds to a dashboard setting under Settings > Chat window:
Two copy buttons export your result: one copies the values to enter in the dashboard, and one copies a runnable createBrandOverrides call for a custom UI. The live component gallery follows the playground’s active settings.
How @layer precedence works
tokens.css wraps every token definition in @layer lovelace.tokens. Layered CSS always loses to unlayered CSS, regardless of specificity or source order.
This is a deliberate escape hatch for your own stylesheets:
- Any unlayered
--lovelace-*rule you write wins over the shipped token defaults. A plain:root { --lovelace-color-background-accent: #0f62fe; }in your CSS is enough. - Provider
overridevalues win the same way, because inline styles beat all stylesheet declarations.
Component styles in style.css are unlayered, hashed classes. Do not target those class names; they change between versions. Extend components through className, style, tokens, and the data-* state attributes instead.
If you need tokens without component styles, for example to build a custom theme or to use the palette in your own components, import @ada-cx/lovelace/tokens.css alone.
Light and dark themes
LovelaceProvider takes a theme prop: "light", "dark", or "auto" (the default).
- The resolved scheme is stamped as
data-theme="light"ordata-theme="dark"on the provider’s wrapper element. - Semantic color tokens are re-declared under each
data-themevalue, so every component inside re-resolves its colors automatically. autofollows the OSprefers-color-schemesetting and updates live when the user changes it.
To read the resolved scheme in your own code, use the useResolvedTheme hook:
Use this hook instead of your own matchMedia query. It resolves through the same code path that stamps data-theme, so your logic and the rendered theme can never disagree.
Theme-dependent color overrides apply in both themes, because inline overrides sit above the data-theme re-declarations. If an override needs a different value per theme, read useResolvedTheme and pass a different override map for each scheme.