Skip to content

Bindings ​

Controls connect to state with bind=. A bound control shows the state's current value and writes to it when someone uses it. Nothing else is needed: there are no change handlers.

sayshape
state agreed=false size="m" qty=1 volume=40 name=""
card
  check "I agree to the terms" bind=agreed
  row
    radio "Small" bind=size value=s
    radio "Medium" bind=size value=m
    radio "Large" bind=size value=l
  stepper "Quantity" bind=qty min=1 max=9
  slider "Volume: {volume}" bind=volume min=0 max=100
  input "Your name" bind=name placeholder="e.g. Ada"
  text "{name == '' ? 'Hello' : 'Hello, ' + name}: {qty} × size {upper(size)}, volume {volume}{agreed ? ', terms accepted' : ''}" tone=muted
Rendered by the SayShape engineOpen in playground

Which components bind ​

Componentbind= holds
check, switch, accordiontrue or false
radioThe chosen radio's value=; every radio in a group shares one bind=
selectThe chosen option's value
date-pickerA date as YYYY-MM-DD text, or "" for none
stepper, slider, rating, paginationA number
inputText, or a number (or null while empty) with kind=number
tabsThe selected tab's value=
tableThe selected row's key= value; sort= and direction= hold its sort
breadcrumb, mapThe pressed step's text, or the clicked country's name

Each component's reference page lists its exact props.

Running actions after a change ​

bind= already writes the value, so don't add on= just to copy it. Use on= for something extra that should happen after the value is stored, like telling your app:

sayshape
state plan="monthly"
card
  radio "Monthly" bind=plan value=monthly on="emit:plan:{plan}"
  radio "Yearly" bind=plan value=yearly on="emit:plan:{plan}"
Rendered by the SayShape engineOpen in playground

The on= actions run with the new value already in place.

Paths ​

bind= can name a field inside a record, or a field of the current item inside each:

sayshape
state settings={theme: "light", alerts: true} todos=[{text: "Water", done: false}, {text: "Map", done: true}]
card
  switch "Alerts" bind=settings.alerts
  each items=todos as=t
    check "{t.text}" bind=t.done
  text "{count(todos, 'done')} of {len(todos)} done" tone=muted
Rendered by the SayShape engineOpen in playground

Writes never change data in place: the whole list or record is replaced with an updated copy, so your app sees a change event for todos or settings and can save it.

What can't be bound ​

  • Derived values. They're read-only (Derived values). Binding to one is reported by the parser.
  • Items an expression builds. each items={[{on: false}]} makes new records that aren't in state, so bind=x.on inside it has nowhere to write. Items of a list worked out from state (filter, sort, search, take) are fine: they write back to the list they came from (Lists).
  • Expressions. bind= takes a name or a path, never {…}.

SayShape v0.1 is an early prototype under the MIT License. Status · GitHub