Theming
Light and dark, and how tokens switch between them.
How themes work
A theme is the data-theme attribute on <html>: light or dark. Every token has a value for each, so changing the attribute recolours the whole page. Components don't need any dark-mode code.
Because themes are attributes, any element can set its own: a data-theme="dark" box shows dark inside a light page. The handbook's example previews work this way.
Switching
- People choose Light or Dark in Settings → Appearance. There's no toggle in the top bar.
- The choice is saved in the browser and applied before the first paint, so a dark-mode user never sees a white flash.
- Tailwind's
dark:follows the same attribute. But don't usedark:to fix a color; fix the token.
// In the root layout's <head>, before anything paints:
<script dangerouslySetInnerHTML={{ __html:
`try{if(localStorage.getItem("theme")==="dark")document.documentElement.dataset.theme="dark"}catch(e){}`
}} />
// On <html>: the attribute changes after the server render, so React shouldn't warn.
<html lang="en" suppressHydrationWarning>Images in dark mode
Logos and graphics with a dark version can't know the theme during the server render. Render a placeholder first, then swap in the right image once the page is running in the browser.
Rules
Colors are tokens
Components use role tokens only: bg-[var(--surface-secondary)], text-[var(--text-muted)]. Never hex, rgb() or Tailwind palette classes such as bg-blue-500, and never a dark: patch to fix a color: if dark mode looks wrong, fix the token.
Not decided yet
Until these are settled, follow Bridge OS as it is today.
- Following the device setting. Bridge OS offers Light and Dark only (no "System"). Whether other apps should follow the device is undecided.
- Each app's identity marker (open question #16): whether apps get one small color of their own, and where it may appear.
Sources
- Bridge OS
DESIGN.md→ Dark Mode. - Package
globals.css(light block answers to:rootand[data-theme="light"]; dark to[data-theme="dark"]).