Get started
Themes
One attribute changes every colour in the system. No component knows which theme is active, because no component references a colour — only a role.
Switching
<html lang="en" data-flux-theme="dark">Light is the default and needs no attribute. Setting it explicitly is still useful when you are scoping a light region inside a dark page.
<!-- In <head>, inline, before any stylesheet or module script.
In a Vite or React app this is index.html, not a component. -->
<script>
const stored = localStorage.getItem('flux-theme');
// Products that follow the OS preference replace 'light' with:
// matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
document.documentElement.setAttribute('data-flux-theme', stored ?? 'light');
</script>// The theme control. Persist the choice so the script above restores it.
function setTheme(theme) {
localStorage.setItem('flux-theme', theme);
document.documentElement.setAttribute('data-flux-theme', theme);
}<html> directly rather than rendering it, because a framework does not own the root element.The three themes
| Theme | Ground | For |
|---|---|---|
light | canvas — the Kaptio off-white | The default. Carries the Flux 1.x values, so an existing page renders as it did. |
dark | neutral-950 | Dense product UI. Neutral rather than teal, so brand accents and status colours still read as signals. |
deep | primary-800 — Kaptio teal | Slides, marketing and brand surfaces. This is the ground Kaptio decks already use; 2.0 gives it a name and a full token set. |
layer-03 aliases layer-02 andtext-placeholder aliases text-subtle. For information-dense interfaces, use dark.All three at once
The same markup, three grounds, no theme-specific code.
Light
September departures
Twenty-four published, three awaiting supplier.
Dark
September departures
Twenty-four published, three awaiting supplier.
Deep
September departures
Twenty-four published, three awaiting supplier.
<div class="flux-card">
<h3 class="flux-card__title">September departures</h3>
<p class="flux-card__body">Twenty-four published, three awaiting supplier.</p>
<div class="flux-card__footer">
<button class="flux-button flux-button--sm">Publish</button>
<span class="flux-badge flux-badge--pending">On request</span>
</div>
</div>Scoping a theme to a region
The attribute works on any element, not only the root.
<main>
<!-- Inherits the page theme. -->
<section class="report">…</section>
<!-- A brand panel on a light page. -->
<section data-flux-theme="deep" style="background: var(--flux-surface-page)">
<h2>Kaptio Edge</h2>
</section>
</main>A scoped region must set its own background from --flux-surface-page or alayer-* token. Setting the attribute changes what the tokens resolve to; it does not paint anything by itself.
Following the system preference
/* Flux does not do this for you, deliberately: a product should decide
whether its users want the OS preference honoured. */
@media (prefers-color-scheme: dark) {
:root:not([data-flux-theme]) {
color-scheme: dark;
}
}Most Kaptio product surfaces are light by default because they sit beside Salesforce, which is light. Honour the system preference where the surface stands alone — an internal tool, a dashboard someone keeps open all day — and let it be an explicit choice elsewhere.
Do and don’t
Do
- Apply the theme attribute before first paint.
- Persist the reader’s choice.
- Give a scoped theme region its own background token.
- Test a new component in all three themes before shipping it.
Don’t
- Do not write theme-specific CSS in a component. If you need to, a token is missing.
- Do not use deep for dense product UI.
- Do not nest three layer levels on the deep theme.
- Do not assume light. Anything that references a primitive will not follow the theme.