# 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.