Themes
Applies light or dark to a page or region, and builds custom themes.
Principles
- Choose a theme with data-theme. Without it the page follows the system setting, and light or dark on <html> fixes the choice.
- Resolve the theme before the first paint. Read the saved choice on the server, from a cookie for example, so the page never flashes the other theme.
- Scope a theme only for previews. Tokens resolve again under any data-theme, which suits an embedded preview but not a second theme on one page.
- Change hue and chroma, never lightness. The builder keeps each step at its lightness, so text contrast stays close to the default palette's, and its report shows the result.
- Leave status colors alone. An accent theme recolors primary actions, checked controls, progress and focus, while success, danger and caution keep their meaning.
Theme selection
Set data-theme on <html>, or on any element to scope that theme to its subtree. The value is 'light' or 'dark'.
To animate a change, see Motion. Saving the choice belongs to your app.
shadcn/ui
shadcn.css maps shadcn/ui variables such as --background, --primary and --radius to Surflane tokens. shadcn components then follow every theme, scoped regions included.
Theme builder
Pick a neutral tint, an accent and a radius scale. The preview, the contrast report and the CSS below follow your choices.
Contrast report
Text pairs in both themes, against the 4.5:1 text minimum.
CSS
Load this with the tokens. It sits in the surflane.themes layer, so import order doesn't matter, and scoped data-theme regions keep working.
Preset files
Each preset ships as a file. Import any neutral, accent and radius file with the tokens.
Themes in code
createTheme returns the same values for your own inputs, and themeCss turns them into CSS.