# Concepts
The mental model behind the whole library. Read this once and the rest of the
docs slot into place.
## The shape of an editor
solid-stage assumes a particular UI: a **world in the middle** (a canvas, a
preview, a node graph) flanked by **dense property panels**. It gives you the
*chrome* around that world — panels, sections, bars, form controls, overlays —
and stays out of the world itself.
```
┌────────────────────────────────────────────┐
│ top bar / toolbars │
├──────────┬───────────────────────┬──────────┤
│ │ │ │
│ left │ center │ right │
│ panel │ (your world) │ panel │
│ │ │ │
├──────────┴───────────────────────┴──────────┤
│ bottom toolbars / bar │
└────────────────────────────────────────────┘
```
That whole frame is **`EditorPanels`**. Its `center` is just `JSX.Element`, so it
can host anything — the chrome is deliberately world-agnostic.
## The pieces, top to bottom
- **Stage** — one `EditorPanels` instance. A root stage owns the window; an
`embedded` stage fills a parent slot (stages can nest — a stage's `center` can
be another stage).
- **Panel** — a collapsible side column (`leftPanel` / `rightPanel`). Collapsing
shrinks it to a strip rather than unmounting it. ([Layout](./layout.md#panel))
- **Section** — a collapsible titled container inside a panel; the unit you build
property UI from. ([Layout](./layout.md#section))
- **Field / Control** — the form widgets inside a section (see below).
- **Bar** — a horizontal strip (top/bottom of the stage, or inside a canvas).
Compose one with **`Toolbar`** (`left` / `center` / `right` cells) or drop in a
**`TabMenu`**. ([Layout](./layout.md#toolbar))
- **Overlays** — app-level singletons that float above everything: tooltip,
modal, toast. ([Overlays](./overlays.md))
## Control vs Field — the key distinction
Every form widget comes in **two tiers**, and knowing which you want saves a lot
of confusion:
- A **`*Control`** is the bare widget — just the input. It takes a `value` and an
`onChange`, its widget-specific props, and an `aria-label`. No caption, no
layout row. Use it when you're placing a widget yourself (e.g. a slider in a
toolbar).
- A **`*Field`** is the captioned form **row** that wraps the control: it adds the
label, the optional description, info bubble, and validation. This is what you
put in a `Section`. It composes the matching control internally.
```tsx
// Bare control — you own the layout:
// Field — captioned row for a panel:
```
Both are public. Reach for the **Field** in a panel; reach for the **Control**
when you need just the widget somewhere custom. Full list in
[Controls & fields](./controls.md).
## Accessors, not values (the Solid convention)
solid-stage is reactive-first. **A widget's value is an accessor — a function you
pass, not a value.** Reads stay fine-grained and the widget tracks changes
without re-creating.
```tsx
const [zoom, setZoom] = createSignal(50);
// ^^^^ the getter itself, not zoom()
```
The same goes for `readonly?: () => boolean`, `disabled?: () => boolean`, etc. —
they're accessors so the widget reacts when they flip. `onChange` is a plain
callback `(next) => void`. If you use `createEditorStore`, `bind(...)` produces a
`{ value, onChange }` pair for you so you never write these by hand.
## Controlled and uncontrolled, everywhere
Anything stateful (a panel's collapse, a section's collapse, a tab selection)
works **two ways**, mirrored consistently:
- **Uncontrolled** — omit the state props; the component manages its own state and
(where it makes sense) persists it to `localStorage`. A `Section` remembers its
collapse out of the box.
- **Controlled** — pass the state accessor + a change handler (`open` + `onToggle`,
`collapsed` + `onToggle`, `tabs` + `onSelect`, …); you own the state and the
component never caches.
The canonical pattern is `isOpen = () => props.open?.() ?? internalOpen()`.
## Theming is CSS variables on `.stage-root`
There are no inline-style props for colours or sizes. Every dimension and colour
is a CSS custom property declared on `.stage-root`; you override by setting that
variable on any container. Defaults use `:where()` (zero specificity), so your
overrides win without `!important`.
```css
.my-editor { --stage-color-accent: #6c9; --stage-border-radius: 4px; }
```
Three **size tiers** (`small` / `normal` / `large`) scale font + control height
together; set one with the `size` prop or a `.stage-size-*` class and everything
inside scales. See [Theming](./theming.md).
## The dock model (movable sections)
By default you author sections as static JSX children of a panel. If you want
users to **drag sections to reorder them, float them over the canvas, or move
them between panels**, you opt into the *dock model*: sections become data in a
`createSectionLayout` controller, panels render `DockZone`s from it, and a single
`StageFloatHost` handles floating. This is entirely optional — static authoring
stays the default. See [Drag, dock & float](./dnd.md).
## Overlays are singletons + a host
The tooltip, modal, and toast are **global singletons** you call imperatively
(`stageModal.open(...)`, `stageToast.push(...)`). The modal and toast each need a
one-time host component (``, ``) mounted once
inside your `.stage-root`; the tooltip mounts itself. See [Overlays](./overlays.md).
## What the library does *not* do
- **No SSR** — browser-only by design.
- **No section registry** — `EditorPanels` and `Section` take children, not id
lists. (The dock model is the opt-in exception, and even it is child-rendered.)
- **No world rendering** — `CanvasStage` gives you sized layers; what you draw in
them is yours.
- **No global state framework** — `createEditorStore` is a convenience store, not
a required architecture.