Slab

SkillFiles & storage

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.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Slab skill

What this skill tells your AI

The instructions your AI receives, as published by stencil-hq/slab in skill/SKILL.md and read by ahel’s review.

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

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

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 groupstack (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/

Signals

GitHub stars
79
Forks
2
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
slab
Source
github.com/stencil-hq/slab