Rich, web-friendly editor UI framework for SolidJS + TypeScript
solid-stage docs theming.md
9.8 kB · 175 lines
Markdown
at main

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

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

// Per-instance via style:
<div class="stage-root" style={{ '--stage-color-accent': '#ff4488' }}>

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 <duration> <easing> 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.

<EditorPanels size="small" ... />          // dense whole stage
<Section title="Details" size="small">     // dense section in a normal stage

Font family is 100% yours (the library font: inherits 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-<name> 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.