Skip to content

Layout components ​

Containers that place other elements, plus each, which repeats elements once per item of a list.

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.

column ​

Stacks its children vertically.

PropTypeDescription
gapnumberSpace between children in pixels (default 8).
padnumberInner padding in pixels (default 0).

Children: any elements.

sayshape
column gap=6
  text "First"
  text "Second"
Rendered by the SayShape engineOpen in playground

Try column interactively →

row ​

Places children side by side, splitting the width equally. On narrow screens they wrap; sibling cards share a height per line by default.

PropTypeDescription
gapnumberSpace between children in pixels (default 10).
padnumberInner padding in pixels (default 0).
aligntop / center / bottomVertical alignment of children: top, center or bottom.
wrapbooleanMove children onto more lines when they don't fit side by side (default true). Set false to always keep them on one line.

Children: any elements.

sayshape
row gap=10
  button "Cancel" variant=secondary
  button "Save"
Rendered by the SayShape engineOpen in playground

Try row interactively →

overlap ​

Puts its later children on top of its first one, at a corner, an edge or the middle: a Sale badge on a picture, a count on an icon, a label over a card. The first child sets the size.

PropTypeDescription
attop-left / top / top-right / left / center / right / bottom-left / bottom / bottom-rightWhere the children on top sit: top-left, top, top-right, left, center, right, bottom-left, bottom or bottom-right.
insetnumberHow far in from the edges they sit, in pixels (default 8).

Children: any elements.

sayshape
overlap at=top-right
  card
    text "Trail runner socks" weight=semibold
    text "Three pairs" tone=muted
  badge "Sale" tone=bad
Rendered by the SayShape engineOpen in playground

Try overlap interactively →

grid ​

Places children in responsive equal columns. Cards in the same grid row automatically share a height, keeping dashboard panels aligned.

PropTypeDescription
minnumberNarrowest a column may be, in pixels (default 200).
gapnumberSpace between children in pixels (default 14).

Children: any elements.

sayshape
grid min=140 gap=10
  stat label="Orders" value=12
  stat label="Returns" value=1
  stat label="Rating" value=4.8
Rendered by the SayShape engineOpen in playground

Try grid interactively →

card ​

A surface that groups related content, stacked vertically. Give it on= to make the whole card pressable.

PropTypeDescription
gapnumberSpace between children in pixels (default 14).
padnumberInner padding in pixels (default 22).
variantraised / hero / tinted / outlineraised (default), hero (an accent wash for the one card that leads the screen), tinted (a quiet accent fill for a side group) or outline (a border only).
onactionActions to run when the card is pressed, e.g. emit:changed. Separate several with ;

Children: any elements.

sayshape
card variant=hero
  text "Order summary" style=title
  text "2 items" tone=muted
card on=emit:open_order
  text "Trail runner socks" weight=semibold
  text "Arrives Thursday" tone=muted
Rendered by the SayShape engineOpen in playground

Try card interactively →

list ​

A bordered list of rows, separated by lines. Use list-item children; any other element (a row, a check) becomes a row of its own.

Children: any elements.

sayshape
list
  list-item "Trail runner socks" detail="Merino blend" trailing="$24"
  list-item "Rain shell" detail="Packs into its pocket" trailing="$129" on=emit:open_shell
Rendered by the SayShape engineOpen in playground

Try list interactively →

list-item ​

One row of a list: a title, an optional detail line and value on the right, and anything inside it (a badge, a check, a row) under the title. Give it on= to make it pressable.

PropTypeDescription
"text"text, requiredThe row's title.
detailstringA second, quieter line under the title.
trailingstringA short value on the right, like a price or a count.
iconstringAn icon before the text, by name from icon's list, e.g. icon=check-circle.
onactionActions to run when the row is pressed, e.g. emit:changed. Separate several with ;

Children: any elements.

Only inside: list.

sayshape
list
  list-item "Rain shell" detail="Packs into its pocket" trailing="$129" icon=package on=emit:open_shell
Rendered by the SayShape engineOpen in playground

Try list-item interactively →

accordion ​

A heading that shows or hides the content under it when pressed. Stack several for an FAQ.

PropTypeDescription
"text"text, requiredThe heading.
bindstate, requiredA true/false state name: true while open.
gapnumberSpace between children in pixels (default 10).
onactionActions to run after it opens or closes, e.g. emit:changed. Separate several with ;

Children: any elements.

sayshape
state returns=true
accordion "Returns" bind=returns
  text "Free returns within 30 days, unworn."
accordion "Shipping" bind=shipping
  text "Ships in two business days."
Rendered by the SayShape engineOpen in playground

Try accordion interactively →

divider ​

A thin horizontal line separating sections.

sayshape
divider
Rendered by the SayShape engineOpen in playground

Try divider interactively →

spacer ​

Empty vertical space.

PropTypeDescription
sizenumberHeight in pixels (default 8).
sayshape
spacer size=16
Rendered by the SayShape engineOpen in playground

Try spacer interactively →

each ​

Repeats what's inside it once per item of a list, in place, with no look of its own; inside, the current item is called item (or the name given by as=). if= on each keeps only the items it's true for: each items=tasks as=t if={owner == 'All' || t.owner == owner}.

PropTypeDescription
itemsexpression, requiredThe list to repeat over, like todos or {cart}.
asnameName for the current item, used inside: as=t makes {t.text} work.
indexnameName for the item's position, counting from 0.
keyexpressionA value unique to each item, like {t.id}, so focus stays with the item when the list changes. Defaults to the position.

Children: any elements.

sayshape
state gear=[{name: "Tent", kg: 2.1}, {name: "Stove", kg: 0.4}, {name: "Headlamp", kg: 0.1}]
column gap=6
  each items=gear as=g index=i key={g.name}
    text "{i + 1}. {g.name}, {g.kg} kg"
Rendered by the SayShape engineOpen in playground

Try each interactively →

timeline ​

Steps in order with a line joining them: itineraries, how-to guides, order tracking, project history. Give steps a status only to show progress, like where an order is or which step the person has reached.

PropTypeDescription
gapnumberSpace between children in pixels (default 20).

Children: step.

sayshape
timeline
  step "Day 1: Sedona" detail="Cathedral Rock at sunrise"
  step "Day 2: Flagstaff" detail="Lowell Observatory at night"
  step "Day 3: Grand Canyon" detail="South Rim, Mather Point"
# Showing progress: a status on every step
timeline
  step "Ordered" time="Oct 18" status=done
  step "Out for delivery" detail="Arrives by 8 PM" status=current
  step "Delivered" status=upcoming
Rendered by the SayShape engineOpen in playground

Try timeline interactively →

step ​

One step of a timeline: a title, an optional detail line and time, and whether it's done, current or upcoming.

PropTypeDescription
"text"text, requiredThe step's title, e.g. Shipped.
statusdone / current / upcomingOnly to show progress: done for finished steps, current for where the person is now, upcoming for what's left (muted). Leave it out on every step for a plan, itinerary or how-to that hasn't started.
detailstringA quieter line under the title, like a place or a note.
timestringA short date or time on the right, e.g. Oct 18 or 9:12 AM.
iconstringAn icon in the step's marker instead of the check or dot, by name from icon's list, e.g. icon=truck.

Only inside: timeline.

sayshape
timeline
  step "Shipped" detail="Left the Phoenix warehouse" time="Oct 19" status=done icon=truck
Rendered by the SayShape engineOpen in playground

Try step interactively →

Cards side by side, a page at a time, with arrows and dots: a few options to browse, like products, hotels or recipes. Put a card per option inside it (or each over a list).

PropTypeDescription
bindstate, requiredThe state name holding the page shown, starting at 0.
minnumberThe narrowest a card may be (default 220); as many fit side by side as the width allows.
gapnumberSpace between children in pixels (default 12).
onactionActions to run after the page changes, e.g. emit:changed. Separate several with ;

Children: any elements.

sayshape
state page=0
carousel bind=page
  card
    text "Desert Rose Inn" style=lead weight=semibold
    text "$189 a night · 4.6 stars" tone=muted
  card
    text "Saguaro Lodge" style=lead weight=semibold
    text "$149 a night · 4.3 stars" tone=muted
  card
    text "Camelback Suites" style=lead weight=semibold
    text "$229 a night · 4.8 stars" tone=muted
Rendered by the SayShape engineOpen in playground

Try carousel interactively →

scroll ​

A region that shows its children up to a fixed height and scrolls the rest, for a long list inside a card: messages, activity, results. Everything inside is still read and copied.

PropTypeDescription
"text"textWhat it holds, for screen readers, e.g. Recent activity. Not drawn.
sizesm / md / lgHow tall it gets before it scrolls: sm (about 6 rows of controls), md (about 9) or lg (about 12).
gapnumberSpace between children in pixels (default 8).
padnumberInner padding in pixels (default 0).

Children: any elements.

sayshape
state events=[{title: "Order NW-2041 shipped", when: "09:12"}, {title: "Refund approved", when: "08:40"}, {title: "New review: 5 stars", when: "Yesterday"}, {title: "Stock low: Ridge 28 L", when: "Yesterday"}, {title: "Invoice sent", when: "Mon"}, {title: "Order NW-2038 delivered", when: "Mon"}, {title: "Price change: Summit pack", when: "Sun"}, {title: "New customer: Lumen Cinema", when: "Sat"}]
scroll "Recent activity" size=sm
  list
    each items=events as=e
      list-item "{e.title}" detail="{e.when}"
Rendered by the SayShape engineOpen in playground

Try scroll interactively →

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