White-label

Put data-brand on an element and set up to four custom properties. Lyra re-derives its accent group from that seed, so the components in that subtree use your brand without per-component overrides.

See it working

Three brands in the current theme

Each card scopes its own data-brand. Use the site theme toggle in the header to see all three brands re-derive in place, without a rebuild. Tab to an input or primary button to see the brand-derived focus ring.

Harbor

Accent soft

Violet

Accent soft

Sunrise

Accent soft

Define a brand

--brand is the required seed. The other three tokens have defaults. Lyra derives readable primary ink automatically; set --brand-contrast only when you want to choose that ink yourself.

TokenDefaultPurpose
--brandRequiredThe brand-color seed.
--brand-contrastAutomatic black or whiteOptional override for text and icons on the primary accent.
--brand-radius10pxThe --radius-md value.
--brand-font"Plus Jakarta Sans", system-ui, sans-serifBoth --font-sans and --font-display.

Here is a complete brand definition:

css
html[data-brand='harbor'] {
  --brand: #0d9488;
  --brand-contrast: #ffffff;
  --brand-radius: 12px;
  --brand-font: 'Plus Jakarta Sans', system-ui, sans-serif;
}

Never set data-brand without a matching --brand definition. The attribute alone activates the derivation layer, and with no seed the whole accent group resolves to nothing — primary buttons lose their fill. To show Lyra's baseline look (for example a "default" option in a brand switcher), remove the attribute instead of defining an empty brand.

Use sentence case for labels

Overline-style labels default to uppercase with their original tracking. For a brand or locale that uses sentence case, add the optional typography tokens to the same brand scope. They work for table headers, sidebar and menu groups, calendar labels, and other overline labels:

css
html[data-brand='harbor'] {
  --brand: #0d9488;
  --overline-transform: none;
  --overline-tracking: normal;
}

--overline-transform: none preserves the case supplied in the markup; write labels in sentence case (for example, Delivery status). Neither token is part of the four brand seed tokens or automatically derived from --brand.

Choose the scope

Put data-brand on <html> when the whole application belongs to one brand. Every descendant uses the derived values.

Put it on a container when only part of the page needs a different identity, such as an embedded workspace preview or a tenant-specific area. Only that container and its descendants receive the brand values, which is how the three cards above coexist.

What Lyra re-derives

In light mode, Lyra keeps --accent as the raw --brand, lightens hover and active with white at 3% and 4% in srgb, and derives --accent-soft, --accent-soft-text, and --focus-ring with color-mix() in oklab. It also sets --on-accent, --text-link, --border-accent, --radius-md, --font-sans, and --font-display.

Inside data-theme="dark", the branded accent, hover, and active mixes use white at 14%, 26%, and 30% in oklab; soft-text and link retain their own white mixes. The dark rule works whether data-theme="dark" and data-brand are on the same element or the branded element is inside the themed one.

Theme islands

A data-theme="dark" container inside a light page works, including a branded descendant. This is useful for a permanently dark panel. The inverse does not work today: light tokens are the defaults on :root, not a [data-theme="light"] block, so data-theme="light" cannot restore light tokens inside a dark ancestor. Use the site theme toggle to preview each brand in both themes.

Keep primary text readable

Lyra derives --on-accent from the resolved accent, choosing neutral black or white automatically. Use --brand-contrast only to pin a specific ink. In browsers that support relative color syntax, the automatic rule meets AA-large (3.0:1) for all supported brand/theme combinations. The known light-mode crossover is #E11D48, which reaches 4.47:1 rather than normal-text AA's 4.5:1; check a custom override before shipping it.

If you also customise --danger to a light colour, set --on-danger to a darker ink: solid danger fills such as .lyra-btn--danger use it and default to #FFFFFF.

What this contract does not change

The contract does not re-derive --surface-* tokens, text colors, spacing, or radii other than --radius-md. It does not override borders beyond --border-accent. If your product needs those to change per tenant, define that layer separately instead of expecting data-brand to do it.