---
name: slab
description: "Writing, editing, and rendering Slab documents (.slab) — the declarative design language for app screens, posters, terminal UIs, and interactive components. Use when authoring or modifying .slab files, rendering via the slab CLI (`bunx @stencil-hq/slab` or `slab-cli`: svg/png/apng/tui), bundling via Vite/Bun plugins (`@stencil-hq/slab/vite`, `@stencil-hq/slab/bun`), embedding via generated web components (`slab gen wc`), typed React components (`slab gen react`), typed Rust modules (`slab gen rust`), typed Go modules (`slab gen go`), `include_doc!` proc-macros, or Ratatui widgets (`slab-ratatui`), declaring the typed host surface (params, list/each, holes, signals, themes, rich-field runs), building rich-text or block editors on kernel fields, working on the conformance corpus, or debugging Slab diagnostics and layout."
---

# Slab

A declarative language for designed surfaces that renders faithfully to web,
GPU, TUI, SVG, and PNG from one source. One pipeline: the Rust compiler
(`slab-compile`) lowers `.slab` text to **SLIR** (protobuf + Snappy binary IR);
one Rust kernel (`slab-kernel`) owns layout, motion, hit testing, focus,
editing, and dispatch on every platform (natively linked, or via WASM in
browsers). Thin drivers paint frames, forward events, and expose the retained
semantic scene through platform accessibility adapters. `spec/SPEC.md` is
normative; `conformance/` is executable (native and WASM match goldens byte
for byte). Hosts never parse `.slab` and never do layout.

The whole layout model in one sentence:

> Every node is a box. Every box receives constraints (min/max width and
> height), returns a size, and its **parent** places it. Containers differ
> only in how they place children.

No margins, no selectors/cascade, no z-index, no absolute positioning, no
display property. Paint order is tree order; overlap requires an explicit
opt-in container (`stack`, `canvas`, `offset`). Mistakes become diagnostics,
not silent overlap.

## Quick start

```slab
tokens {
  color { bg #0e1116; ink #e6edf3; accent oklch(72% 0.16 250) }
  text  { title { size 18; weight 650 } }
}

def Chip(label, tone=color.accent) {
  row pad=4,10 gap=6 radius=999 stroke=tone align=center w=hug {
    rect w=6 h=6 radius=3 bg=tone
    text label size=12 color=tone nowrap
  }
}

col#card w=360 pad=24 gap=12 bg=color.bg radius=12 {
  text "Pale Green Things" style=text.title color=color.ink
  row gap=8 align=center {
    Chip label="FLAC"
    spacer
    text "4:12" size=12 color=color.ink
  }
}
```

Render (no Rust toolchain needed; the compiler ships as WASM in the npm CLI):

```sh
bunx @stencil-hq/slab render doc.slab -o out.png --width 800
bunx @stencil-hq/slab check doc.slab      # ALWAYS run after editing
```

Output kind infers from the extension (`.svg .png .apng .txt`); `--client tui`
with no `-o` prints cells to stdout. `check` validates the main document and
each `export` definition through its standalone path. Code generators produce
typed web components (`slab gen wc`), React wrappers (`slab gen react`),
Rust modules (`slab gen rust`), or Go modules (`slab gen go`). In this repo
the native CLI is
`cargo run -p slab-cli --` (adds `fmt`, `conformance`, `lsp`, and
`--font`).

Fresh releases: bun's minimum-release-age gate blocks packages younger than
24h (`… blocked by minimum-release-age`). Fix: add `[install]
minimumReleaseAge = 0` to a project `bunfig.toml`. `bunx` ignores a cwd
bunfig — use `bun add @stencil-hq/slab` then `./node_modules/.bin/slab`.

## Core vocabulary (memorize; details in references/language.md)

- **Containers**: `row col wrap grid stack canvas para group` — `stack`
  (layers) and `canvas` (`at=x,y`) are the ONLY overlap opt-ins.
- **Leaves/controls**: `text`, `span` (inside `para`), `rect`, `img`, `path`
  (canvas only), `icon`, `divider`, `spacer`, and `hole`.
- **Sizing** per axis: `w=240` (request) | `hug` | `fill` / `fill:2` | `40%`
  + clamps `min-w max-w min-h max-h`. Defaults: main = hug; cross = stretch
  for containers and `rect`, hug for other leaves; inside `stack`/`canvas`
  everything hugs.
- **Style**: `bg stroke stroke-w stroke-align stroke-sides stroke-dash radius
  smooth shadow blur backdrop backdrop-mask grain mask opacity color family
  size weight leading tracking strike italic underline code-color code-bg
  style= align-text rotate scale tilt fit pad gap animate transition
  scrollbar scrollbar-w scrollbar-fg scrollbar-bg` — closed set, nothing
  else. `current` is icon-declaration paint, not a general color token.
- **Flags/modes**: `clip bleed scroll nowrap ellipsis inert focusable
  multiline drag-ghost`; use `drag-ghost` only with `drag=`, and use
  `scroll=cross|both`, `sticky`, and `each … virtual item-extent=N` only in
  their documented contexts.
- **Reserved attrs**: `key=`, signal binders (`act=`, pointer/drag lifecycle,
  field/submit, resize), `keys=`, a11y metadata/state/relation/value attrs,
  and overlay placement `attach= gravity= collide=`.
- **Conditionals**: `when hover|dragging|drop|tui|dark|w<600|prop|theme(name)
  { … }` patches its node with attrs/children. Binders on the patched node are
  statically registered, active only while the condition holds, and stop
  contributing focus/hit behavior when false.
- **Components/data**: `def Name(params) { body }`, Capitalized calls,
  children splice at `slot`, and `export` defs become standalone documents
  and recursive `list(Def)` schemas. `each param.rows` consumes a root list;
  nested templates use `each child_prop`. Macro expansion has no arithmetic.
- **Host surface** (typed, compiler-checked): scalar params, recursive lists,
  holes, runtime image registration, signals, keyed scroll/divider/reveal
  APIs, field caret/runs/range editing, and the retained scene. There is NO
  tree injection or selector API.

## Rules that prevent 90% of mistakes

1. `fill` is a SIZE keyword, never a color — backgrounds use `bg=`.
2. `align=` positions a node's CHILDREN; to position the node in its parent
   use `self=` (stack children: `self=bottom-end offset=4,-4`).
3. `%` needs a determinate parent axis; against `hug` it degrades to hug with
   `pct-unbounded`. Progress bars live inside `fill`/fixed tracks.
4. Node headers end at newlines. End each continued header line with `\`;
   indentation alone does not continue it.
5. Bare idents in value position are keywords or component props — token
   references are ALWAYS dotted (`color.accent`, never `accent`).
6. Numbers are unitless `u` (1u = 1px on web/svg). Durations are plain ms.
7. One shadow inline (`shadow=0,2,6,#0004`); layered shadows must be a list
   of presets/token refs (`shadow=shadow.crisp,shadow.lift`).
8. TUI paints one cell per grapheme, but layout uses vector font metrics.
   Use cell-multiple geometry and `pad=16,8`+ inside borders. Set the full
   triplet `when tui { family="mono" size=13.333 leading=1.2 }`: the
   600/1000em mono advance becomes exactly 8u (one column) and the line box
   13.333 × 1.2 = 16u (one row). The default leading 1.4 yields 18.67u lines,
   so any stacked text column drifts off the 16u row grid.
9. Quarter-turn `rotate` (±90/270) participates in layout; any other angle is
   ink-only. TUI skips rotated subtrees.
10. Dynamic rows come from `list(Def)` + `each`, including recursive child
   lists. Give items stable keys; path-address nested lists by index/field.
   Use kernel virtualization only for a uniform-height top-level `each`.
11. Diagnostics are the contract: `squeeze` = fixed size clamped, `clipped`
    = content truncated, and `glyph-missing` = static text is absent from its
    resolved embedded family. Fix the named source; never silence by guessing
    coordinates. `cap-*` names a declared client degradation.
12. Keep policy in the host: Slab owns layout, gesture mechanics and optional
    drag ghosts, focus, scrolling, text editing (caret, selection, rich runs,
    IME, bounded undo), scene export, and shipped web/native accessibility
    adapters. The host owns app state, block structure, popover dismissal,
    and focus traps.
13. Treat `spec/SPEC.md` as normative and `spec/FRAME.md` as the exact host
    ABI. Skill references are procedural guidance, not replacement specs.

## Feature selection cues

- Use a recursive `list(Def)` plus nested `each child_prop` for trees and
  grouped rows. Add `virtual item-extent=N` only to a direct root-list `each`
  under a main-axis scrolling `row`/`col`; use `revealItem` for navigation.
- Use `para { each param.runs }` for host-supplied rich text; make the run
  schema body exactly one `span`.
- Use `field=` text nodes for editing: the kernel owns caret, selection,
  wrapping, IME, and bounded undo. Rich text keeps five inline span sets
  (bold/italic/underline/strike/code) beside the string via the field-runs
  API; toggle styles over the selection; `code-color`/`code-bg` paint code
  runs. For a block editor keep one field per block: Shift-click forms a
  cross-field range, edits arrive as one pre-mutation `range_edit` request,
  and structural undo uses the snapshot/commit/restore field transaction.
- Use `path d=param.route` (inside `canvas`) for runtime geometry. Declare
  reusable static `icon` assets at top level and tint them through `current`.
- Use `img src=param.name` plus host image registration for runtime pixels;
  do not encode changing image data into document params.
- Use `press/context/dblclick` for gesture starts, `pointer-move/pointer-up`
  for routed raw motion/releases, and `drag/drag-update/drag-end/drop` for a
  complete drag lifecycle. Consume every signal's typed `SignalMeta`; add
  `drag-ghost` when the kernel should paint the moving source duplicate.
  Use `act=` for ordinary keyboard-and-pointer activation.
- Use `scroll=cross|both` for two-axis overflow, `sticky` only on direct
  main-scroll children, and keyed `reveal` instead of host-computed offsets.
- Use `divider` between two panes; let it own pointer/keyboard resizing.
  Supply initial/restored extents through the keyed API, not bespoke dragging.
- Use `attach=param.anchor` on a `stack`/`canvas` child for popovers. Feed a
  signal's full `meta.key` back as the anchor; keep dismissal/focus policy in
  the host.
- Author complete a11y roles, names, state, relations, values, and live-region
  metadata. Shipped web/native adapters build the platform semantic tree;
  application hosts do not rebuild it from scene records.

## References — load on demand

- **references/language.md** — grammar and node semantics; dynamic
  paths/icons; para runs; scrolling/sticky; dividers; anchored overlays;
  accessibility attrs; layout, components, tokens, keys, and diagnostics.
  Read when authoring beyond basic screens or when a diagnostic is unclear.
- **references/styling.md** — gradients, icon `current` paint,
  layered/inset shadows, glass, blur, stroke geometry, transforms,
  interaction states/drag ghosts, and motion. Read when styling effects,
  icons, interactions, or animation.
- **references/hosts.md** — recursive/virtual lists, runtime images, pointer
  and drag signals with `SignalMeta`, generated web/Rust/Go bindings, the
  exact clean-cutover Instance APIs, scroll/reveal/divider state, popovers,
  rich-field runs and caret/range editing, host structural transactions, and
  framework accessibility adapters. Read when building an interactive or
  data-driven app.
- **references/rendering.md** — SLIR → kernel → Frame, runtime path/image and
  scale ops, scene semantics, per-client degradations, TUI rules, CLI, and
  conformance. Read when implementing a driver, targeting a client, or
  debugging cross-client differences.

When the repo is available, `spec/SPEC.md` is normative (`spec/SLIR.md` and
`spec/FRAME.md` for the machine interfaces) and `examples/*.slab` are quality
references — `examples/10-settings.slab` is the canonical interactive app
(params, signal buttons, kernel-edited field, hole);
`examples/12-tracklist.slab` shows `list`/`each`, themes, and scrollbars;
`examples/01-settings.slab` and `06-jcard.slab` are the visual-quality bar
for static documents; `bench/slate/doc/editor.slab` is the block-editor
reference (one field per block, rich runs, cross-field ranges). Live
playground: https://stencil-hq.github.io/slab/
