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