Themes
How @infinitibit_gmbh theme packages work, how to use one, and how to add a brand theme.
Themes
A theme is a published package that provides the design tokens for one brand.
@infinitibit_gmbh/ui defines the token contract (the --ib-* variables); a theme
package supplies their values. v1 theming is build-time: a product app
installs exactly one theme and imports its stylesheet.
Using a theme (consuming teams)
Install one theme and import its CSS once at your app entry:
import '@infinitibit_gmbh/theme-default/theme.css';
That single file is the complete token set — light values at :root and dark
values under .dark (see Dark mode). You do not separately
import @infinitibit_gmbh/ui/tokens.css in a themed app; the theme supersedes it.
Then build with the tokens as usual (see Design Tokens).
Available themes
| Package | Use | Distinct from default |
|---|---|---|
@infinitibit_gmbh/theme-default | the default brand | — (the baseline) |
@infinitibit_gmbh/theme-docs | this docs site | primary remapped to the brighter blue ramp; one step rounder control radius |
How themes are built
Each theme reuses @infinitibit_gmbh/ui's DTCG token source and layers its own
overrides, via Style Dictionary:
theme-default→ ui source, no overrides (≡ the defaults).theme-docs→ ui source +overrides/*.tokens.json(loaded last, so they win).
The build is dual-mode — two Style Dictionary passes (light + dark semantic
sources) concatenated, via the shared tokens-source/sd-dual-mode.mjs helper
(each theme's build-tokens.mjs calls it). Each theme emits two artifacts
(dist-only — built in CI, not committed):
theme.css→:root { …light… }+.dark { …dark… }— the production import.theme.scoped.css→[data-theme="<name>"] { … }+[data-theme="<name>"].dark { … }— used by Storybook's brand + mode toolbars.
Because themes regenerate from ui's source, they always carry exactly the ui
token contract and can't drift from the UI version they ship with. They're
also in the Changesets fixed group with @infinitibit_gmbh/ui, so versions move
together. See ADR 0009
and ADR 0016 (dual-mode).
Dark mode
Themes ship light and dark values: light at the theme selector, dark under a
.dark class. Mode is orthogonal to brand — brand rides [data-theme], mode
rides .dark — so any brand works in either mode.
To enable dark mode, toggle the .dark class on <html> (or any ancestor):
<html class="dark"> … </html>
With a theme manager like next-themes, use attribute="class" and it adds
.dark for you:
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
Components need no changes — they read --ib-color-*, which resolve to the
dark values whenever .dark is active. Load the base font too (Inter):
import '@fontsource-variable/inter'.
Adding a brand theme
- Create
packages/theme-<brand>/(copytheme-docs's shape). - Add
overrides/*.tokens.jsonredefining only the token paths you want to change (e.g.palette.primary.*,radius.*). Everything else inherits the defaults. - Add a
build-tokens.mjsthat callsbuildDualModeTheme({ buildPath: 'dist/', brand: '<brand>', overrideSources: ['overrides/**/*.tokens.json'] })from../ui/tokens-source/sd-dual-mode.mjs(copytheme-docs's). pnpm --filter @infinitibit_gmbh/theme-<brand> build→theme.css+ scoped variant (each with light:root/[data-theme]+ dark.darkblocks).- Add the package to the Changesets
fixedgroup so it stays version-locked.
The product app then imports @infinitibit_gmbh/theme-<brand>/theme.css.
Previewing themes in Storybook
Storybook imports the scoped variants, so the Brand toolbar switches stories
between default, docs, and any registered brand, and the Mode toolbar
(light/dark) toggles the .dark class — for design review/QA. To add a brand
to the toolbar, import its theme.scoped.css in .storybook/preview.ts and add
it to the withThemeByDataAttribute map.
Dark mode is supported (toggle
.dark, see above). Runtime brand switching (an end user toggling brand live) is still not a v1 default — it requires an RFC.