# Theming
The library is themed entirely through **CSS custom properties**. There are no
inline-style props for colours or dimensions. You override a token by setting it
on `.stage-root` (or any container inside it).
```css
.my-editor {
--stage-color-bg: #1b1d22;
--stage-color-accent: #6cc;
--stage-border-radius: 4px;
}
```
Defaults are declared with `:where(.stage-root)` (specificity 0), so your
overrides win **without `!important`**. The library never writes `!important`
either.
```tsx
// Per-instance via style:
```
## Conventions
- Every class is `stage-*`. Every dimension and colour is a `--stage-*` variable.
- Tokens **derive from a few roots, parent-by-parent**. The whole grey ground keys
off `--stage-color-bg` (panel lifts off bg, header darkens off panel, border
lifts off panel, the input well darkens off panel…). So retinting one root flows
to its children, while pinning a single token stops the chain there.
- Surface colours are **opaque — no alpha** (the one exception is
`--stage-color-modal-backdrop`, a scrim). Don't reintroduce alpha into the ramp.
- The **accent family** (`--stage-color-accent` + `-readonly` + `-on-accent` +
`-text-on-accent` + `-icon-on-accent`) is a separate "primary" axis; it does
*not* derive from bg.
- **Content on an accent fill** has its own colours: `--stage-color-text-on-accent`
(the "on"/active button label, the highlighted dropdown option, the active search
row) and `--stage-color-icon-on-accent` (icons in those spots). The text defaults to
**white**, which suits the default saturated accent — but a theme with a **bright accent + light text** must override
`--stage-color-text-on-accent` (and, if needed, `-icon-on-accent`) to a dark,
legible colour, or on-accent content washes out. (`--stage-color-on-accent` is
separate: the bright fill for the decorative switch thumb / checkbox tick.)
## The most useful tokens
### Colour
| Token | Role |
|---|---|
| `--stage-color-bg` | Root background; the whole grey ramp derives from it. |
| `--stage-color-panel-bg` | Panel surface. |
| `--stage-color-popover-bg` | Dropdowns / popovers. |
| `--stage-color-section-header-bg` | Section header strip. |
| `--stage-color-input-bg` | Input well. |
| `--stage-color-input-readonly-bg` | Readonly input fill. |
| `--stage-color-control-hover-bg` | The single hover fill for every interactive control (buttons, steppers, triggers, panel/tab chrome). |
| `--stage-color-border` / `--stage-color-border-strong` | Borders. |
| `--stage-color-border-hover` | Border lift on hover (bordered buttons, select triggers). |
| `--stage-color-text` | Primary text. `-text-label` and `-text-muted` equal it by default, so they don't dim until you override them. |
| `--stage-color-text-disabled` | Text on a disabled button. Solid, mixed from text and the panel background. |
| `--stage-color-accent` | Primary/accent (checked toggles, active states). |
| `--stage-color-accent-hover` | Brightened accent for hovering an accent-**filled** control a click would change — a checked switch/checkbox, an active multi-toggle (all turn *off* on click). Lifted toward white off `--stage-color-accent`; the neutral `--stage-color-control-hover-bg` covers the off/unfilled state. |
| `--stage-color-text-on-accent` | Text on an accent fill (active button, highlighted option, active search row). Pure white by default (full contrast on the saturated-blue accent); override to a dark colour for a pale accent. |
| `--stage-color-icon` | Base icon colour. Defaults to `currentColor` (matches surrounding text). |
| `--stage-color-icon-on-accent` | Icon on an accent fill. Defaults to `--stage-color-text-on-accent`. |
| `--stage-color-text-hover` | Label/glyph of a hovered button — brightens to the on/active colour so a hover reads as the same "lit" state. Defaults to (and tracks) `--stage-color-text-on-accent`; override to decouple. |
| `--stage-color-on-accent` | Bright fill for the decorative switch thumb / checkbox tick. |
| `--stage-color-accent-readonly` | Accent under readonly. |
| `--stage-color-focus` | The focus indicator colour (default **white**, deliberately accent-independent so it stays legible on an accent-filled control). Every bordered control — value controls, buttons, the colour field, and joined input+action groups — recolours its **border** to it on focus, the top of a cascade-layer border-state hierarchy, so it overrides the hover/checked/validation/resting border while focused and restores on blur. Joined groups coordinate: a focused segment also whites the seam it shares with its neighbour, so the recolour is real (no box-shadow ring). |
| `--stage-color-info` / `-success` / `-warn` / `-error` | Status levels (toast border, validation). |
| `--stage-color-modal-backdrop` | Modal scrim (the one translucent token). |
### Corner radius (one knob)
| Token | Default | Covers |
|---|---|---|
| `--stage-border-radius` | `4px` | **Everything** — inputs, select trigger, buttons, dropdowns, the modal — so the whole app rounds uniformly. |
| `--stage-border-radius-lg` | `= base` | Alias of the base (kept because a lot of CSS references it). |
| `--stage-border-radius-modal` | `= base` | Alias of the base. |
Set `--stage-border-radius: 0` for a fully-square look. To bring back a rounder
tier for the bigger surfaces, override `-lg` / `-modal` (e.g.
`--stage-border-radius-lg: calc(var(--stage-border-radius) * 2)`).
### Motion (one derived knob)
| Token | Default | Notes |
|---|---|---|
| `--stage-motion-duration` | `0.18s` | Base duration. |
| `--stage-motion-ease` | `ease` | Base easing. |
| `--stage-panel-motion` / `--stage-section-motion` / `--stage-toast-motion` | derived | Per-interaction ` ` pairs. |
Retune everything by overriding the base, or one interaction by pinning its own
token. Set any duration to `0s` to disable that motion. Animations are **on by
default**; `@media (prefers-reduced-motion: reduce)` zeroes the base
automatically.
## Size tiers
The library **owns the type scale**: font size and control height are coupled and
ship as three hand-tested discrete tiers (a 1px border doesn't scale with `em`, so
it's tiers, not a formula).
| Tier | font / height | Selector |
|---|---|---|
| `small` | 11px / 16px | `.stage-size-small` |
| `normal` (default) | 13px / 20px | unclassed |
| `large` (touch) | 16px / 24px | `.stage-size-large` |
Set a tier with the **`size` prop** (`EditorPanels`, `Panel`, `Section`, `Label`)
or a **`.stage-size-*` class** on any element. It cascades: set it on the stage and
everything inside scales; re-set it on a section to override just that subtree.
```tsx
// dense whole stage
// dense section in a normal stage
```
> Font *family* is 100% yours (the library `font: inherit`s it). Font *size* is a
> library knob because layout is downstream of it — an off-tier font-size forfeits
> the row-height guarantee. `.stage-root` and the tier classes set it, so custom markup
> in a section reads at the tier's size rather than the page's. Set `font-size` on your
> own class to change that markup. To return a subtree under a `small`/`large` ancestor to
> normal, use the explicit `.stage-size-normal` class (the default is *unclassed*).
Many dimensions derive from the tier: `--stage-expanded-width` (panel width),
`--stage-bar-height`, `--stage-icon-size`, checkbox/switch/slider sizes, etc. — so
a `large` stage gets wider panels and taller bars for free.
On **touch devices** (`@media (pointer: coarse)`), text inputs bump their
font-size to `--stage-font-size-touch` (16px) so focusing one doesn't trigger iOS
Safari's zoom (it zooms any focused input under 16px). This is font-only — the row
height, label, and other controls stay dense — and on by default; opt a field out
with `touchSizing={false}`. The `small` tier is left as-is (its row is too short to
seat 16px) and accepts the zoom.
## Component dimension tokens
For fine-tuning individual widgets (most consumers never touch these):
`--stage-field-value-width`, `--stage-section-body-pad`, `--stage-textarea-{min,max}-height`,
`--stage-select-listbox-max-height`, `--stage-search-max-{width,height}`,
`--stage-slider-track-height`, `--stage-colorpicker-{width,area-height,…}`,
`--stage-tab-max-width`, and the per-level tooltip skins
`--stage-tooltip-{info,success,warn,error}-{bg,text,border}`.
## Z-index tokens
`--stage-z-popover` (1000) < `--stage-z-modal` (1050) < `--stage-z-toast` (1060) <
`--stage-z-tooltip` (1100). Inside a stage: `--stage-z-stage-world` (1) <
`-stage-hud` (20) < `-stage-pointer` (30); floating panels use
`--stage-z-float` (40).
## Where tokens live
All defaults are in the library's `theme/tokens.css` (imported by `styles.css`).
Browse that file for the complete, authoritative list and the derivation formulas.
## Which classes are stable
You can override anything, since the CSS is yours to fight with. These classes are the
ones that won't change without a mention in the changelog:
| Class | What it is |
|---|---|
| `.stage-root` | The token scope |
| `.stage-size-small`, `-normal`, `-large` | The size tiers |
| `.stage-icon`, `.stage-icon-` | Icons |
| `.stage-input` | The text input surface |
| `.stage-button` | The shared button base |
| `.stage-field-control` | A field row |
| `.stage-compact`, `.stage-expanded`, `.stage-bar` | A row's layout |
Every other `.stage-*` class is internal. Selecting one still works, but it's an escape
hatch that a release can break. Prefer a token or a `class` prop: `Label`, `Section`,
`SectionDef`, `FieldBar` and `stageModal.open` all take one.