Skip to content

Loading, success and failure ​

SayShape doesn't fetch data or submit forms itself. Your app does, and tells the interface how it went by setting state. A status value and a few if= lines cover most cases.

sayshape
state status="loading" orders=[]
card
  text "Recent orders" style=title
  if={status == 'loading'}
    skeleton lines=3
  if={status == 'ready' && len(orders) == 0}
    empty "No orders yet" detail="Orders you place show up here."
  if={status == 'ready' && len(orders) > 0}
    list
      each items=orders as=o key={o.id}
        list-item "{o.name}" trailing="{money(o.total)}"
  if={status == 'failed'}
    text "Orders couldn't be loaded." tone=bad
    button "Try again" variant=secondary on=emit:retry
  row
    button "Show loaded" variant=secondary on="set:status:'ready';set:orders:[{id: 1, name: 'Tent', total: 180}, {id: 2, name: 'Stove', total: 45}]"
    button "Show failure" variant=secondary on="set:status:'failed'"
Rendered by the SayShape engineOpen in playground

The buttons at the bottom stand in for your app. In a real page:

ts
ui.state.set("status", "loading");
try {
  ui.state.set("orders", await fetchOrders());
  ui.state.set("status", "ready");
} catch {
  ui.state.set("status", "failed");
}
ui.on("action", ({ name }) => { if (name === "retry") load(); });

Submitting ​

The same pattern works the other way. The spec emits, your app does the work and reports back:

sayshape
button "Save" on=emit:save disabled={saving}
text "Saved" tone=good if={saved}
ts
ui.on("action", async ({ name }) => {
  if (name !== "save") return;
  ui.state.set("saving", true);
  await save();
  ui.state.set("saving", false);
  ui.state.set("saved", true);
});

notify: messages, or ui.notify("Saved") from your app, are a lighter way to confirm something happened.

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