Skip to content

Elements and nesting ​

A SayShape spec is a list of lines. Each line is one element: its type, then optional quoted text, then name=value props.

sayshape
button "Save changes" variant=secondary on=emit:save
Rendered by the SayShape engineOpen in playground

Props ​

A prop value can be written three ways:

FormExampleUse it for
Barevariant=secondarySingle words and numbers
Quotedlabel="Water (L)"Text with spaces or symbols
Expressionvalue={price * qty}Anything computed from state (Expressions)

Quotes inside quoted text are escaped with a backslash. Quoted text can also include {expressions}, which are filled in from state:

sayshape
state guests=2 nights=3
text "Cabin for {guests} guests, {nights} nights"
text "She said \"see you there\"" tone=muted
Rendered by the SayShape engineOpen in playground

Each component accepts the props listed in the component reference, and every element also accepts if= (Conditional visibility) and tip=. An unknown prop or a value outside a prop's allowed list is a warning: the element still renders and the bad part is ignored.

Tips ​

Every element accepts tip=, a short explanation shown in a small bubble over it on hover, on keyboard focus, or on a tap on a touch screen. Escape hides it. An element that isn't pressable becomes a Tab stop when it has a tip, so the keyboard can reach it, and screen readers read the tip after the element's name. Use tips for what a label or a number means, not for anything people must read to use the screen.

sayshape
state open=14
card
  row
    stat label="Open orders" value={open} tip="Paid, not yet handed to the carrier"
    icon name=info tip="Counts refresh every 5 minutes"
  button "Ship all" on=set:open:0 tip="Marks every open order shipped"
Rendered by the SayShape engineOpen in playground

Nesting ​

Indent children under their container. Two spaces is the convention; a tab counts as two spaces.

sayshape
card
  text "Your order" style=title
  row
    button "Cancel" variant=secondary
    button "Pay"
Rendered by the SayShape engineOpen in playground

The top level of a spec behaves like a column: unindented elements stack vertically. Some elements only go inside a particular parent, like list-item inside list or tab inside tabs; the reference says where each one can go. Elements can nest at most 100 levels deep.

Indentation is the only structure. There are no closing lines, which is what lets a half-written spec render: every line is complete on its own (Streaming).

Lines that aren't elements ​

LineMeaning
state name=value …Declares state (State)
derive name={expression} …Declares a derived value (Derived values)
each items=… as=…Repeats what's indented under it (Lists)
if={condition}Shows what's indented under it only while the condition holds (Conditions)
# a commentIgnored
sayshape
# Starting values come first.
state ready=false
card
  check "Ready to go" bind=ready
  text "Let's go!" tone=good if=ready
Rendered by the SayShape engineOpen in playground

No colors, fonts or sizes ​

Specs never set colors, fonts or pixel sizes. They choose roles instead, and the theme decides what each role looks like:

sayshape
text "Section" style=title
text "Saved" tone=good
text "Card declined" tone=bad
button "Delete" variant=danger
badge "New" tone=accent
Rendered by the SayShape engineOpen in playground

A few layout props take numbers (gap=, pad=, size=, min= on a grid), which are spacing in pixels within the theme's design, not styling. This keeps every generated screen consistent with your app, and it's why a request for "a big red button" can't be followed literally.

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