Input components
Controls people press, type into or drag. Most read and write a state name through bind=.
Generated from the component definitions in src/components, the same source the parser validates against and the model guide is built from. Every element also accepts if=, tip=. Each example below is rendered live by the SayShape engine.
button
A pressable button that runs actions. Use emit: to tell the app what the user chose.
| Prop | Type | Description |
|---|---|---|
"text" | text, required | The button label. |
on | action | What happens when pressed, e.g. inc:count, set:done:true, emit:book. Separate several with ; |
variant | primary / secondary / danger | primary (filled), secondary (outlined) or danger. |
disabled | condition | Expression; when true the button is greyed out and can't be pressed. |
button "Book now" on=emit:book
button "Back" variant=secondary on=dec:steplink
Pressable text for a secondary action, lighter than a button.
| Prop | Type | Description |
|---|---|---|
"text" | text, required | The link text. |
on | action, required | What happens when pressed, e.g. emit:size_guide. |
link "Size guide" on=emit:size_guidecheck
A checkbox that toggles a true/false state value.
| Prop | Type | Description |
|---|---|---|
"text" | text, required | The label next to the box. |
bind | state, required | The state name this checkbox reads and writes. |
state agreed=false
check "I agree to the terms" bind=agreedswitch
An on/off switch with a label, for settings that take effect right away.
| Prop | Type | Description |
|---|---|---|
"text" | text, required | The label for the setting. |
bind | state, required | The true/false state name this switch reads and writes. |
on | action | Actions to run after it is switched, e.g. emit:changed. Separate several with ; |
state updates=true
switch "Email me order updates" bind=updatesradio
One choice among several. Give each radio in a group the same bind= and its own value=.
| Prop | Type | Description |
|---|---|---|
"text" | text, required | The label for this choice. |
bind | state, required | The state name the group shares; it holds the chosen value. |
value | string, required | What bind= is set to when this radio is chosen. |
on | action | Actions to run after it is chosen, e.g. emit:changed. Separate several with ; |
state size=m
radio "Small" bind=size value=s
radio "Medium" bind=size value=m
radio "Large" bind=size value=lselect
A field that opens a list to pick one option from, for choices like a department, status or filter; compact where radio buttons would take too much room. bind= holds the chosen option's value; with multiple=true, a list of the chosen values, for filters like regions to show.
| Prop | Type | Description |
|---|---|---|
"text" | text | The label above the field. |
items | expression, required | The options: a list of records, or of text or numbers. At most 200. |
value | name | For records, the field holding each option's value (text or a number), e.g. value=id. Options without one, or repeating one, are left out. |
label | name | For records, the field shown for each option, e.g. label=name. Default: the value. |
bind | state, required | State that holds the chosen value, like department or t.status. |
multiple | boolean | Pick several options: bind= holds a list of their values, starting from a list in state like regions=["West", "East"] (or [] for none), and the list stays open while choosing. filter(sales, 'region', regions) keeps the items whose field is any chosen value; an empty list keeps none, so show everything then: {len(regions) ? filter(sales, 'region', regions) : sales}. |
placeholder | string | Shown while nothing (or a value no option has) is chosen. |
unavailable | name | A field that, when true, greys an option out so it can't be chosen, e.g. unavailable=full. |
disabled | condition | Expression; when true the field is greyed out and doesn't open. |
on | action | Actions to run after an option is chosen, with bind= already set, e.g. emit:changed. Separate several with ; |
state teams=[{id: "eng", name: "Engineering"}, {id: "sales", name: "Sales"}] team=""
select "Team" items=teams value=id label=name bind=team placeholder="Choose a team"date-picker
A field that opens a month calendar to pick one date, kept in bind= as YYYY-MM-DD text ("" for none), or with range=true a start and an end. For deadlines, bookings or report filters.
| Prop | Type | Description |
|---|---|---|
"text" | text | The label above the field. |
bind | state, required | State that holds the date as YYYY-MM-DD, like due or t.date. Start it as "" for no date. |
min | string | The earliest date that can be chosen, YYYY-MM-DD, included. |
max | string | The latest date that can be chosen, YYYY-MM-DD, included. |
range | boolean | Pick a start and an end date: bind= holds {from, to} as YYYY-MM-DD, starting from a record in state like period={from: "", to: ""}. The calendar offers Last 7 days and Last 30 days. filter(orders, 'date', period) keeps the items from the start to the end, both included, and all of them while it's empty. |
placeholder | string | Shown while no date is chosen. |
required | boolean | When true, the calendar has no Clear button. |
disabled | condition | Expression; when true the field is greyed out and doesn't open. |
on | action | Actions to run after the date changes, with bind= already set, e.g. emit:changed. Separate several with ; |
state due=""
date-picker "Due date" bind=due min="2026-01-01" max="2027-12-31"Try date-picker interactively →
stepper
A number with minus and plus buttons, for small counts like a quantity.
| Prop | Type | Description |
|---|---|---|
"text" | text | An optional label on the left. |
bind | state, required | The number state this stepper reads and writes. |
min | number | Smallest value (default 0). |
max | number | Largest value (no limit if left out). |
step | number | How much each press changes the value (default 1). |
on | action | Actions to run after the value changes, e.g. emit:changed. Separate several with ; |
state qty=2
stepper "Quantity" bind=qty min=1 max=10slider
Picks a number in a range by dragging a handle, or with the arrow keys. Good for prices, distances and amounts.
| Prop | Type | Description |
|---|---|---|
"text" | text | An optional label above the slider. Show the value with an {expression}. |
bind | state, required | The number state this slider reads and writes. |
min | number | Smallest value (default 0). |
max | number | Largest value (default 100). |
step | number | The value moves in steps of this size (default 1). |
on | action | Actions to run after the user lets go, or after each arrow key, e.g. emit:changed. Separate several with ; |
state maxPrice=120
slider "Up to ${maxPrice}" bind=maxPrice min=20 max=300 step=10input
A text field people type into, such as a name, an email, a search, a new item, or with lines= a note or a message. Each keystroke writes its bind= state, and required= and kind=email or number say what's wrong once it's been left.
| Prop | Type | Description |
|---|---|---|
"text" | text | The label above the field. Leave it out only when the screen makes the purpose obvious. |
bind | state, required | The state name or path it reads and writes, like task or t.title. |
placeholder | string | A hint shown while the field is empty, like e.g. Sunscreen. |
kind | text / email / number / password / search | text, email, number, password or search. Picks the phone keyboard and autofill; number writes a number, or null while empty. email and number also say when what's typed isn't one. |
lines | number | Rows of text, 1 to 12: more than 1 is a box for a note or a message, where Enter starts a new line (on= then runs only from a button). |
required | condition | When true, an empty field says Required once it's been left. |
error | string | Your own message, shown under the field once it's been left, while it isn't empty, e.g. error="{len(pw) < 8 ? 'At least 8 characters' : ''}". |
on | action | Actions to run when Enter is pressed, after the value is written, e.g. emit:changed. Separate several with ; |
disabled | condition | Expression; when true the field is greyed out and can't be typed in. |
max | number | Longest value in characters (at most 10000; 2000 when lines= is more than 1). |
state task="" todos=[]
input "New item" bind=task placeholder="e.g. Sunscreen" on="add:todos:{task};set:task:''"
text "{plural(len(todos), 'item')}" tone=mutedsegmented
Two to six short options in one track, one chosen, like Week, Month and Year, or Chart and Table. Show different content for each with if=.
| Prop | Type | Description |
|---|---|---|
bind | state, required | The state name holding the chosen option's text. |
label | string | What the choice is about, read by screen readers, e.g. label="Period". Default: the text just above it. |
options | string, required | The options, comma-separated, e.g. options="Week,Month,Year". At most 6, one or two words each. |
on | action | Actions to run after the choice changes, e.g. emit:changed. Separate several with ; |
state view="Chart"
segmented bind=view options="Chart,Table"
text "Showing the {lower(view)}" tone=mutedmenu
A button that opens a short list of actions, for what can be done to one item (edit, duplicate, delete) without a row of buttons. Each menu-item runs its own on=.
| Prop | Type | Description |
|---|---|---|
"text" | text | The button's words, e.g. Actions. Leave it out for a small ⋯ button. |
label | string | What a screen reader calls a ⋯ button with no text, e.g. label="Order actions". Default: More actions. |
variant | secondary / primary | secondary (outlined) or primary (filled). |
disabled | condition | When true, it can't be opened. |
align | start / end | Where it sits in its room: start, or end (the right). Default: end for a ⋯ menu, start for one with words. |
Children: menu-item.
menu "Actions"
menu-item "Edit" icon=file on=emit:edit
menu-item "Duplicate" icon=copy on=emit:duplicate
menu-item "Delete" icon=x-circle tone=bad on=emit:deletemenu-item
One action in a menu's list.
| Prop | Type | Description |
|---|---|---|
"text" | text, required | What it does, e.g. Duplicate. |
on | action, required | What happens when it's chosen, e.g. emit:duplicate or set:confirm:true. Separate several with ; |
icon | string | An icon before the text, by name from icon's list, e.g. icon=check-circle. |
tone | default / bad | bad for an action that removes or can't be undone, drawn in the bad color. |
disabled | condition | When true, it's shown but can't be chosen. |
Only inside: menu.
menu "Actions"
menu-item "Duplicate" icon=copy on=emit:duplicate