Elements

Guide

Theming

Five dimensions on the root element — values, defaults, auto-resolution, and how to override tokens yourself.

The whole look hangs on five attributes written to the root element (<html>): data-theme, data-accent, data-scene, data-density, data-contrast. The theme engine in @bysages/core writes them and persists; you can also set them by hand for static pages.

ts
import { applyTheme } from "@bysages/core";

applyTheme({
  mode: "dark",        // "light" | "dark" | "system"   (default "system")
  accent: "zhusha",    // "auto" | "ink" | "qinghua" | "celadon" | "zhusha" | "feicui" | "jilan" | "qingjin"   (default "auto")
  scene: "civic",      // "auto" | civic | enterprise | studio | tech | cupertino | expressive | fluent | material | sketch | missive | dispatch | metric | new-york
  density: "spacious", // "compact" | "default" | "comfortable" | "spacious"  (default "default")
  contrast: "high",    // "auto" | "normal" | "high"    (default "auto")
});

The call merges with the current theme, writes the attributes, and returns the full Theme. Persistence goes to localStorage key bs-theme (opt out with persist: false); initTheme() restores it and keeps mode: "system" tracking the OS.

#Mode — paper and lacquer

data-theme="light" is the paper; data-theme="dark" is warm lacquer-black — never #000 — with the same hue relationships lifted to brighter steps. "system" resolves through prefers-color-scheme at apply time.

#Accent — the mineral pigments

data-accent lays a pigment under primary action blocks and the focus halo. Semantic colors — success, warning, danger, info — are fixed and never move with the accent.

accentReads asNote
ink (or unset)monochrome inkthe default solemn primary
qinghuacobalt blue青花
celadonpale water green天水碧
zhushacinnabar redseal-paste red, civic identity
feicuijade green翡翠, the handheld register's pigment
jilanazure blue霁蓝, the office register's pigment
qingjinlapis blue青金, the metric register's pigment
autothe scene's pairingsee the table below

#Scene — thirteen temperaments

data-scene retunes shape, density, motion, and the direction of light across the whole interface — no component forks. Two families:

sceneNameServesPaired accentPaired contrast
civic典章elder-facing and government desktops — square corners, 48px targetszhushahigh
enterprise信笺quiet commercial long sessionsqinghuanormal
studio雅集design and editorial, literati whitespaceceladonnormal
tech司南modern precision and throughputinknormal
cupertino圆融soft, ethereal handheld registerqinghuanormal
expressive飞白playful, motion-firstzhushanormal
fluent流水restrained productivity suiteqinghuanormal
material格物elevation register, layered by shadowceladonnormal
sketch写意hand-drawn charmzhushanormal
missive家书the familiar handheld registerfeicuinormal
dispatch公牍the office handheld registerjilannormal
metric格律the metric enterprise registerqingjinnormal
new-york玄素the monochrome developer registerinknormal

Where each register comes from. The audience scenes — civic, enterprise, studio, tech — are our own desks. The style scenes are homages to one design language each, renamed to a common word so the API stays ours; the lineage is the point, so it is stated plainly:

sceneBorrows the temperament of
cupertino 圆融the iOS design language — soft, airy, handheld
expressive 飞白spring-motion-led mobile design
fluent 流水Fluent, the measured productivity register
material 格物Material, the elevation-led layered register
missive 家书the familiar phone register of open mobile style specs (WeUI)
dispatch 公牍WeUI for Work, the same register dressed for the office desk
metric 格律TDesign, the metric enterprise design system
sketch 写意hand-drawn sketchbooks and comics
new-york 玄素shadcn/ui, the monochrome developer register (its own style name)

applyTheme({ scene }) applies the pairing automatically; an explicitly set accent or contrast always wins over the pairing — applyTheme({ scene: "civic", accent: "qinghua" }) keeps civic geometry and speaks in cobalt. The pairing lives in SCENE_DEFAULT_ACCENT / SCENE_DEFAULT_CONTRAST if you need it in code.

#Density and contrast

data-density scales whitespace only — gaps, paddings, control spacing — never type size or contrast. data-contrast="high" lifts secondary text and hairlines to primary strength and deepens the focus halo: the same design, louder.

#Overriding tokens

Themes select values; your CSS can replace them. Tokens live in @layer bs.tokens, so an unlayered override wins without specificity games:

css
:root {
  --bs-radius-sm: 4px;   /* square-cut the seals further */
  --bs-color-primary: var(--bs-color-ink);
}

Scope an override by mounting the attributes on a subtree instead of <html> — the attributes cascade, so a data-scene="studio" panel can sit inside a civic page. getTheme() reads the current logical theme; initTheme() is the whole boot for restore-and-follow-OS behavior.