Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155S2Ye8BRUakV7GHAsmYWu
13 KiB
UI kit reference
The kit is called the Composer: app/ui/composer/. It is the only code that styles or builds raw DOM. For when to use which element, read UI design guidelines. This page is the reference.
How elements work
import { composer } from "../../composer/composer.js";
const save = composer.button.md({ label: "Save", tone: "primary", onClick: save });
host.appendChild(save.node);
save.setBusy(true);
// later
save.destroy();
- An element is
composer.<group>.<variant>(props). It returns a handle:{ node, destroy, ...methods }. nodeis the DOM node to place.destroy()removes listeners and the node. A feature that creates elements aftersetupreturns must destroy them itself.- Methods differ by element. The common ones are
setValue,get,setText,setItems,setDisabled,setBusy,setPressed,openandclose. Read the factory incomposer/elements/<file>.jsfor the exact set. - Content is text. Every string prop goes through
asText(internals/text.js) and is shown as text, never parsed. A prop that would take HTML does not exist. - Containers take handles, not nodes or markup:
panel.default({ children: [handleA, handleB] }). - Unknown elements throw
UnknownElement.composer.button.huge(...)fails with the list of known variants. A feature that asks for an element that does not exist therefore hides itself instead of showing something approximate. - The kit's own CSS classes start with
cx-. App frame classes start withfp-(app/ui/frame.css) and belong to the shell. Never use either from a feature; the feature rule test fails on acx-orfp-string.
Where each group lives
| File | Groups |
|---|---|
text.js |
brand, code, header, hint (low: true hides it first when the row runs short), label, text |
button.js |
button, buttonGroup, iconButton |
input.js |
filterList, input, number, search, stepper, textarea |
choice.js |
checkbox, checkboxRow, checklist, color, radioGroup, segmented, select, toggle |
slider.js |
range, slider |
layout.js |
actionBar, card, collapsible, field, frame, group, hero, list, panel, region, settingsRow, sidebar, splitPane, tabs, toolbar, workspace |
tile.js |
grid, tile |
stage.js |
stage |
status.js |
banner, chip, dot, dropzone, emptyState, inlineError, progress, skeleton, spinner, toast |
modal.js |
modal |
popover.js |
autocomplete, menu, popover, splitButton, tooltip |
floating.js |
floating (window, corner), overlay, slideOver |
gallery.js |
gallery, ruler, viewer (media, cover) |
track.js, nle.js |
track, timeline |
wheel.js |
wheel |
Elements
Variants are listed after the group. Props shown are the ones every use needs; the catalogue (catalogue/elements.html) and catalogue/demos.js show each element with a working set.
Text
| Element | Variants | Main props |
|---|---|---|
header |
display, xl, lg, md, sm |
text |
text |
lg, md, sm, xs |
text. Has setText. |
hint |
default |
text |
label |
field, section |
text, for |
code |
inline, block |
text, label (block) |
brand |
default |
name |
Actions
| Element | Variants | Main props |
|---|---|---|
button |
xl, lg, md, sm, menu |
label, onClick, tone (neutral, primary, danger, ghost, render), icon, disabled, busy, type, title, pressed. Methods setBusy, setDisabled, setPressed, setLabel. |
iconButton |
md, sm, micro |
icon, label (required), onClick, tone, disabled, pressed |
buttonGroup |
md |
label, value, items |
splitButton |
md |
label, items, onClick |
Inputs and choices
| Element | Variants | Main props |
|---|---|---|
input |
md, sm |
value, placeholder, label, onCommit |
search |
md |
placeholder, label |
textarea |
md |
value, rows, label |
number |
md |
value, min, max, step, precision, unit, snap, onChange, disabled, id, label |
stepper |
md |
value, min, max, step, label |
slider |
md, sm, readout, macro |
value, min, max, step, label, onChange, onCommit. .setMax(n) changes the top and redraws the fill. |
range |
md |
from, to, min, max, step, label |
toggle |
default |
label, hint, checked, onChange |
checkbox |
default |
checked, label |
checkboxRow |
default |
label, hint, checked |
checklist |
default |
label, values, items |
radioGroup |
default |
label, value, options |
segmented |
md, sm |
label, value, options, onChange |
select |
md, sm |
label, value, options, onChange |
filterList |
md |
value, items |
autocomplete |
default |
anchored to an input; sits above modals |
color |
swatch |
label, value |
wheel |
picker, half |
edge, items |
dropzone |
default |
label, hint |
options is a list of { value, label }. Changes are reported as the value, not as an event.
Layout
| Element | Variants | Notes |
|---|---|---|
frame |
app |
The page frame. Owned by the shell. |
workspace |
docked |
Docked areas around a centre. |
splitPane |
h, v |
Two children with a draggable divider. |
region |
stack |
children, gap, label, fill. .set(children) replaces them. |
panel |
default, zone |
A titled card. |
group |
default |
A quiet sub-block. |
field |
default, row |
A label, a control and a hint. |
settingsRow |
default |
The row the Settings window uses. .setHint(text) changes its hint later. |
card |
default |
children. Related fields on one rounded card (a region.stack with a background). |
grid |
pair, cards |
children. Tiles in two columns, or as many columns as fit (at least 190 wide). |
tile |
action, node, add |
title, sub, onClick. node also takes state (ready, needs, skipped) and draws a dot. add is the dashed empty slot. |
hero |
default |
title, hint. The mark, a big title and one line (the top of About). |
collapsible |
default |
|
tabs |
underline, dock |
value, tabs. |
sidebar |
rail |
value, items. |
toolbar, actionBar |
default, sticky |
Toolbars wrap; an action bar sticks to the bottom of its scroll area. |
list |
rows |
Grouped rows. |
Status and feedback
| Element | Variants | Main props |
|---|---|---|
banner |
info, warn, danger |
text |
chip |
neutral, good, warn, danger, info |
label |
dot |
default |
tone, label |
inlineError |
default |
text |
toast |
info, good, warn, danger |
text, action, duration (default 3200 ms) |
progress |
bar, indeterminate, inline |
value, max, label. inline is a short bar with a line of text beside it: .setValue, .setText, .setHidden. |
spinner |
md, sm |
label |
skeleton |
line, block, grid |
count, label |
emptyState |
default |
icon, title, hint |
Overlays
| Element | Variants | Main props |
|---|---|---|
modal |
generic, stacked, dialogue, prompt, choice |
generic: title, subtitle, size (sm, md, lg, xl), body, actions, onClose, closeOnOutside, closeOnEsc. dialogue: title, message, tone, confirmLabel, cancelLabel, onResult, and a .result promise. |
popover |
anchored |
Positioned by internals/anchor.js, flips at an edge. |
menu |
dropdown, context |
A context menu is anchored to a point, not an element. |
tooltip |
default |
|
floating |
window, corner |
window is a draggable window (the Settings and Sampler windows). corner is a small button pinned inside the bottom-right corner of anchor (a handle or a node); it follows the box and calls onGone when the box leaves. |
slideOver |
md |
A panel that slides in from the side. |
overlay |
blocking |
message. Blocks the page while something runs. |
All overlays use the portal, the z-ladder and the dismiss stack.
Media and time
| Element | Variants | Main props |
|---|---|---|
gallery |
adaptive, icons, list, rows, cards, strip |
id, cols, items. Density through internals/density.js. |
viewer |
media, cover |
media: src, empty; .setLook({ transform, filter, fit, opacity }) changes how the picture is drawn (kept across sources). cover is a layer over its parent that shows a picture, clip or sound with a close button: .show({ src, kind }), .close(), onClose. |
stage |
overlay |
A transparent layer over a picture holding text and pictures that can be dragged and resized. Gives data (items: position as 0..1, size in picture pixels, colours, text); onMove(id, {x, y}) and onResize(id, factor) report numbers, never styles. .fit(w, h) sizes it to the picture inside its parent, .setItems(items) draws, .busy is true during a gesture. |
ruler |
default |
label, total, marks |
track |
default |
label, pxPerSecond, playhead, selection, items |
timeline |
stage |
pxPerSecond, lanes. Methods setLanes, setPlayhead, setZoom. |
Internals
Used by elements. A feature does not import them. They exist in composer/internals/:
| File | Does |
|---|---|
register.js |
define, UnknownElement, lookups. |
el.js, text.js |
Safe DOM building, asText, setText. |
ids.js |
Unique ids for label and control pairs. |
zlayer.js |
The z-ladder. The only place a z-index is written. |
portal.js |
Where overlays are drawn. |
dismiss.js |
One Escape and outside-click stack. |
focus.js |
trap and roving. |
anchor.js |
Pure placement (measure, flip, clamp) plus anchorTo. |
drag.js, dragReorder.js, snap.js, swapping.js |
Dragging, reordering and snapping. |
density.js |
Grid columns 0 to 4, saved per id. |
motion.js |
Reduced motion check and shared timings. |
Adding an element
- Write it in the right file under
composer/elements/(or a new file). Calldefine(group, variant, factory)once per variant. The factory takes props and returns a handle. Use onlyel,asTextand the internals. Never writeinnerHTML. - Register the file. A new file is imported in
elements/index.jsand its stylesheet is added tocomposer.cssbeforepolish.css. - Style it with tokens only, using
cx-class names. Cover both themes and reduced motion. - Add a demo to
catalogue/demos.jsunder"<group>.<variant>". A registered element with no demo fails the suite. A demo is a props object, or a function that receivescomposerwhen the props need other handles. - Teach the content test any new string prop name.
composer/tests/rule_content_is_data.test.jsattacks every string under the names inCONTENT_KEYSwith a markup payload and proves it comes back as text. If your prop name is not in the set (for examplecaptionorempty), add it, or the test fails with a message naming the demo. - Test behaviour in
composer/tests/elements_behaviour.test.jsor a new*.test.jsbeside it: what the element does when used, not only that it renders. - Look at it in
catalogue/elements.htmlin dark and light.
Rule tests that run over the whole kit
| Test | Proves |
|---|---|
rule_content_is_data.test.js |
Every string prop of every demo is shown as text. |
rule_unknown_element_hides.test.js |
Asking for a missing element throws and hides the feature. |
rule_features_use_the_kit.test.js |
Every file in app/ui/features/ passes FEATURE_RULES (no document, window, style, class names, literal colours or sizes). |
rule_kit_never_interprets.test.js |
tools/kit_lint finds no markup parsing or eval in the kit and the shell, and still catches a bad example. |
register.test.js |
Duplicate definitions are refused. |
overlays.test.js, dismiss.test.js, zlayer.test.js, portal.test.js |
Layering, Escape and portal behaviour. |
panel_schema.test.js |
panel.js draws every setting type. |
panel.js and the catalogue
composer/panel.js renders a module's SETTINGS as a panel with the allowed ui renderers. The live app does not use it for Settings (it uses features/settings/field.js); the catalogue and the tests do. So a ui value changes what the catalogue shows, not what Settings > Engine draws. See Module reference.
The catalogue
Open /funpack/app/ui/catalogue/ with the devserver running (python tools/devserver.py 8199).
| Page | Shows |
|---|---|
index.html |
Tokens: type, colour, spacing, radius, motion. |
elements.html |
Every registered element in both themes. |
hide.html |
What happens when a module is broken or missing. |
log.html |
The Log view. |