Skip to content

Binders & events

The complete shorthand vocabulary a component template may use. Every token is compiled — none of these are interpreted at runtime.

Events

Event Fires on Notes
@click click allowed everywhere (incl. :each rows)
@dblclick double click top-level only
@submit form submit preventDefault() applied; sends the form's fields with a command — the idiomatic submit (never @click on a button whose default type=submit would reload)
@input input top-level only; pairs with :bind for validate-as-you-type
@escape Esc keydown top-level only

The value is an action program: ;-separated signal = expr assignments and handler calls — see Handlers & commands.

Core binders

Binder Meaning Constraint
:text="expr" textContent tracks the expression
:show="expr" element hidden unless truthy; focuses a child [autofocus] on reveal
:class="name: expr" toggle one class on a condition
:attr="name: expr" set/remove an attribute (falsy/None removes; True → empty attr)
:bind="signal" two-way input binding; type=number inputs coerce to JS numbers; the focused field is never repainted mid-keystroke the signal must be Local
:each="row in coll" the keyed client list reconciler coll is a Local[list] (or an enclosing row's field); the binder goes on a wrapper — never on the same element as other binders
:tree="row.children" self-recursive row template (recursive lists, comment threads) inside a :each
:autohide="signal" clear a status signal back to its default after ~4 s

Inside a :each row

Row-scoped binders compile against the row object: :text/:show/:class/:attr/:bind all work with t.field reads; events are restricted to @click/@submit. A row's identity is its id field (reconciliation is keyed on it). Server-rendered rows expose fields to the client through data-* attributes — t.field in a binder on a data-id row reads the row's dataset. Such a row must declare its type: data-row="<Record>" on the row element plus a matching TypedDict in the .py, so each DOM string is decoded to its real type (an integer id, a boolean flag). Every field a binder reads must be in the record and shipped as data-<field>; an undeclared client-read row is a CompileError. (declui derives the record from your model; the unified idiom's named projection ~ Record[…] is the same declaration.)

Transport binders

Binder Meaning Requires
:virtual="handler(scroll)" a windowed viewport: only the visible rows in the DOM, fetched from the mount's /window route a server handler + a Local scroll signal; mount_component(window_template=...)

Widget binders (kit-internal wiring)

Policy: an interactive kit widget is a component; the binder below is its internal wiring, not the public API. Each self-contained widget ships as a situ_ui component — <Tabs>, <DataTable>, <DataGrid>, <Combobox> / <ComboboxMulti>, <Menu>, <CommandPalette> — and the component is how you use it; the kit catalog is the canonical reference. The raw binder is the escape hatch for a bespoke host that can't splice a component (declui's codegen emits one directly, for instance). Either way the runtime holds only presentation state and your data and selection stay in Locals.

Binder Component (use this) App-owned state
:tabs <Tabs> — (bar generated from data-tab panels)
:table="rows" <DataTable> Local[list]; header-click sort
:datagrid="rows" + :datagridsel="sel" <DataGrid> Local[list] + Local[set] — sort + filter + pagination + selection
:combobox="value" <Combobox> / <ComboboxMulti> (multi via data-multi) Local[str] / Local[set]
:menu <Menu> — (items keep their compiled @click)
:palette <CommandPalette> — (⌘K overlay)

The other two runtime binders have no self-contained component — you compose them onto your own markup (like :each), so they stay first-class author binders:

Binder Meaning App-owned state
:sortable="list" pointer drag-reorder over a :each list (<Sortable> is a thin convenience wrapper) Local[list]
:cells="grid" the spreadsheet formula engine — the one sanctioned runtime interpreter, for user-typed formulas Local[dict]

The island feature-detects each runtime (S.tabs && S.tabs(el)), so pages that don't load widgets.js are unaffected.

The unified idiom (opt-in)

With mount_component(..., unified=True):

Construct Meaning
{ expr } one interpolation — server-rendered or live, derived from the expression's sites
:each="t in coll ~ [f1, f2]" the loop with its wire projection: the complete per-row field list allowed to cross to the browser; anything else is a CompileError

See The unified idiom.