Skip to main content
To create a custom theme, override BEEQ’s root semantics — the brand, intent, and neutral colors — with your own values. BEEQ’s light and dark mode roles already point to those root tokens, so every component picks up your palette in both modes. Override individual mode roles only where your palette needs a different mapping.
Set --bq-brand and the other root semantics here, in the theme. To style UI with the brand color, use the mode role --bq-ui--brand instead, as described in Color architecture.
Namespace your theme using the bq-theme attribute (e.g., bq-theme="my-custom-theme") or a matching CSS class (e.g., .my-custom-theme).

Step-by-step guide

1

Define root semantics

Create a CSS file for your custom theme (e.g., custom-theme.css) and set the root tokens. Give each intent you change all five steps — see Intent scale for what each step is used for:
Don’t set --bq-focus here. Focus is a mode role that defaults to --bq-brand-strong in light mode and --bq-brand-bright in dark mode, so it follows your brand automatically.
2

Adjust mode roles (optional)

Override mode roles only where the defaults don’t fit your palette. The most common cases are the on-color of a solid fill, the focus color, and neutral surfaces:
The full list of mode roles and their defaults is in Colors → Mode roles. Hover and active colors aren’t roles you set per component: BEEQ mixes them from the rest color with --bq-hover and --bq-active, so they follow your overrides.
3

Apply your custom theme

Include your custom theme CSS file in your project and set the bq-theme and bq-mode attributes on the <html> element.
bq-mode is optional for light mode: without it, the root element uses BEEQ’s light mode roles plus the :not([bq-mode]) overrides from the previous step. Set bq-mode="dark" (or the .dark class) to switch to dark mode.
Always load beeq.css before your custom theme CSS so that your variables correctly override the defaults.
Pick the setup that matches your project:
The @beeq/tailwindcss preset maps BEEQ tokens to Tailwind utility classes but does not define your custom theme. Keep the custom theme CSS as a separate file that overrides --bq-* variables, and load it after beeq.css.
With Tailwind v4, @beeq/tailwindcss 1.16.2 and earlier fail with @plugin (b is not a function). On those versions, use @config "@beeq/tailwindcss"; instead.
With Tailwind v4, import beeq.css with layer(base) as shown. Without it, the BEEQ reset overrides Tailwind utilities such as mx-auto. See Apps that use cascade layers.
Set the theme and mode on the root element from your app entry so the attributes are present before any component renders:
To switch to dark mode, change the bq-mode attribute:
You can also use CSS classes instead of HTML attributes:
For a full React setup — packages, styles, icons, and event handling — see the React framework guide. For the Tailwind utility classes that BEEQ ships alongside your custom theme, see Colors → Tailwind CSS.

Full example: Nebula theme

Nebula pairs a fuchsia brand with a cyan accent on violet-tinted neutrals, and adds rounder corners and Space Grotesk type. It changes every component in three short layers:
  1. Root semantics. Brand, accent and the neutral scale, plus the font and radius tokens. BEEQ’s mode roles read from these, so light and dark mode follow on their own.
  2. Mode roles. Four dark-mode overrides: solid brand and accent fills switch to their bright steps, with dark text on top.
  3. Component variables. Two component-level choices: cards sit on the raised surface with a hairline border, and the progress bar uses the accent instead of the brand, on a cyan-tinted track in dark mode.
Nebula doesn’t set info, success, warning or danger, so those keep BEEQ’s values: the info alert stays blue while the brand turns fuchsia. Toggle the mode in the preview to compare both palettes.
Every Nebula value passes the contrast checklist in light and dark mode. If you change a color, check the pairs it affects again.

How overrides work

BEEQ wraps every theme and mode selector in :where(), for example :where([bq-mode="dark"]). :where() has zero specificity, so any selector you write — :root, .promo, [bq-theme="my-custom-theme"], or a component tag — wins over BEEQ’s defaults, whichever stylesheet loads first. You don’t need !important or extra selector weight.
Mode roles reference root semantics (e.g., --bq-ui--brand: var(--bq-brand)), and CSS resolves var() on the element that declares the role. Roles are declared on the theme/mode element, usually <html>, so overriding a root semantic deeper in the tree doesn’t re-derive the roles below it. Override the role itself in that case.

Override levels

Pick the narrowest level that covers your change:
Change root semantics on :root (or your theme selector). Every mode role that points to them updates in both modes:
A mode role set on :root (e.g., --bq-text--on-brand) applies in both modes. Use the per-mode level if light and dark need different values.

Contrast checklist for custom brands

BEEQ’s own themes are checked by an automated contrast test. When you bring your own colors, check these pairs in both modes before you ship. All ratios follow WCAG 2.1 Level AA.
Mid-tone brand colors often can’t reach 4.5:1 with white and stay readable on hover. If white text on your brand fill fails, either use a darker step for the fill in light mode (e.g., --bq-ui--brand: var(--bq-brand-strong)) or pick a dark on-color. Then check the hover and active mixes again — they shift the fill toward a dark neutral.