Invoice INV-2041
PaidInvoice INV-2041
Paidimport { Badge } from "@hel/ui/badge"
import { Button } from "@hel/ui/button"
import { Field, FieldControl, FieldLabel } from "@hel/ui/field"How it works#
A theme is a block of CSS variables, scoped to a data-theme attribute. Components never name a color. They read tokens such as --mapped-surface-base-page and --mapped-text-base-primary, and the theme decides what those mean. Light and dark come built in, and dark is also set on :root, so it applies when no theme is chosen.
Both panels in the preview above use the same components and the same markup. The only difference is data-theme="light" on one and data-theme="dark" on the other. Because the theme is just an attribute on any element, you can theme a whole page, a sidebar, or a single card.
For the tokens behind the colors, and how to write a whole theme file, see Configuration.
Set it up#
HelProvider mounts the theme provider for you. To use it on its own, wrap the app once.
import { ThemeProvider } from "@hel/ui/theme"
<ThemeProvider defaultTheme="system">
<App />
</ThemeProvider>The provider sets data-theme on the page root before the first paint, remembers the choice in localStorage, follows the operating system while the choice is "system", and keeps several tabs in step. A stored name that is not a known theme is ignored, so an old value cannot stick.
Import#
import { ThemeProvider, useTheme } from "@hel/ui/theme"Examples#
Switching#
useTheme() gives you the theme that was chosen, the theme that is applied, the list of themes, and setTheme. The chosen theme can be "system", and the applied one is always a real theme name, which is why the two can differ. Build your switch from theme and setTheme, and show resolvedTheme when you need to know what is on screen.
The preview below themes only the box around it, with the target prop, so the rest of this page does not change. In your app you will not pass target, and the whole page changes.
import * as React from "react"
import { Badge } from "@hel/ui/badge"Changing a few values#
Because the components read tokens, you can change how a part of the page looks by setting a few of them on an element. Here eight tokens turn a card blue. This is a good way to try an idea, or to tint one area such as a marketing section.
import type { CSSProperties } from "react"
import { Badge } from "@hel/ui/badge"For a theme that you will reuse, write all the tokens in one CSS file under [data-theme="ocean"], import it after @hel/ui/styles.css, and add the name to themes. A token you leave out falls back to the dark value, so a complete theme defines all of them. Point each value at an --alias-color-* token and not at a literal color, so a change to the tokens still changes the theme.
<ThemeProvider themes={["light", "dark", "ocean"]}>{children}</ThemeProvider>Avoid a flash on load#
On a server-rendered page the first paint happens before the provider runs, and it uses the default theme (dark). If the visitor picked light, they may see dark for a moment. To avoid it, set data-theme from a small script in the <head> that reads the stored choice before the page is drawn. The key is hel-theme unless you changed storageKey.
Props#
ThemeProvider#
| Prop | Type | Default |
|---|---|---|
themes | string[] | ["light", "dark"] |
defaultTheme | string | "system" |
systemThemes | { light: string, dark: string } | { light: "light", dark: "dark" } |
storageKey | string | null | "hel-theme" |
forcedTheme | string | none |
target | HTMLElement | null | the page root |
themes are the names that can be chosen, and each one needs a matching [data-theme] block of CSS. systemThemes says which themes "system" maps to, so an app with "sepia" and "midnight" can follow the system between those. storageKey={null} does not remember the choice. forcedTheme locks one theme and ignores the stored choice, which suits a preview.
useTheme#
| Value | Meaning |
|---|---|
theme | What was chosen: a theme name, or "system". |
resolvedTheme | The theme applied right now. |
themes | The names that can be chosen, without "system". |
setTheme(name) | Choose a theme, or "system". It is remembered when storage is on. |