Live pages
Polls, forms, checklists and reactions turn a shared page into something readers can act on. Responses are stored for you, with no backend, no API key, and no JavaScript of your own.
A shared page is usually something people read. A live block lets them act on it: vote, submit, tick, add, upvote, react. You drop one tag into your HTML and the storage is handled.
No backend. No API key. No JavaScript of your own. The block is the whole contract.
Which block do I want
The question that decides it is who writes, and who gets to see it.
| I want readers to | Use | Everyone sees |
|---|---|---|
| choose between options I wrote | <smp-poll> | the tallies |
| send me something private | <smp-form> | nothing, only you |
| tick off shared work | <smp-checklist> | the ticks |
| add items others can see and back | <smp-checklist allow-add allow-vote> | the items and their scores |
| signal they saw it, without writing | <smp-reactions> | the counts |
The split that catches people out is form versus checklist. A form's
submissions are private to you and never appear on the page. A checklist is
shared, and allow-add extends that to the items themselves. If a reader should
add something the next reader can see, that is the checklist, not the form.
What every block shares
These rules hold for all of them, so the sections below only cover what is particular to each.
An id is required, and permanent. It is the key responses are filed under.
Keep it unique on the page and do not change it later; changing it orphans
everything collected so far.
Everything is anonymous. A block shows that eleven people reacted or that an item is ticked, never who. Only a form knows who wrote what, and only in your own export. For attribution, use comments instead.
Broken markup degrades, it never breaks the page. A block with no id, or
with no usable children, is left exactly as the plain text you wrote. A typo
costs you the feature, not the page.
Your CSS owns the look. Blocks render into your page's own HTML with no
styling of their own, and expose data-smp-* hooks to style against. Each
section lists its own; the convention is [data-smp-<thing>] for elements and
data-smp-<thing>-count for numbers, so a progress readout or a highlight is
CSS alone.
Other people's changes arrive on their own. Every block re-reads every few seconds and when you return to the tab, so two people on the same page stay in step without refreshing.
You can close or reset any of them. Open the page's settings, and under Responses: Close freezes a block read-only, Reset clears it for a fresh run. Responses only appears once a page actually has live blocks on it.
Fair-use caps apply: a per-page response cap plus per-visitor and per-IP rate limits. Sized for pages that collect responses, not for high-volume data collection. See Data and backends if you are past that, or need to read individual rows back.
Polls
<smp-poll id="lunch">
<smp-poll-question>Where are we going?</smp-poll-question>
<smp-poll-option value="sushi">Sushi</smp-poll-option>
<smp-poll-option value="pizza">Pizza</smp-poll-option>
</smp-poll>Each option becomes a button with a live count and bar. One choice per person: clicking a second option moves your vote rather than adding one.
<smp-poll-question>is optional; any text you already have works as the question.- Each
<smp-poll-option value="...">needs a stablevalue. The text inside the tag is the visible label. - Add
require-nameplus one<input data-smp-name>on the page to capture each voter's name into your export. The vote is blocked until it is filled, and the block getsdata-smp-need-nameso your CSS can prompt.
Hooks: [data-smp-poll="id"] on the container with data-smp-total;
[data-smp-poll-option="value"] on each button, with [data-smp-poll-count] and
[data-smp-poll-bar] inside; data-smp-poll-closed when closed.
For picking several things at once, a poll is the wrong shape: use a
checklist with allow-vote, which stores a
row per person per item and can show which ones are yours.
Forms
<smp-form id="signup" multiple>
<smp-field name="name" label="Your name" required></smp-field>
<smp-field name="size" label="T-shirt size" type="select">
<smp-option value="m">Medium</smp-option>
<smp-option value="l">Large</smp-option>
</smp-field>
<smp-field name="notes" label="Anything else" type="textarea"></smp-field>
</smp-form>Free-text intake. Submissions are private to you and never render on the page; you read them from Export CSV in the page's settings, one column per field.
typeis one oftext(default),textarea,select,radio,checkbox,number,email. The choice types take<smp-option value="...">children.requiredblocks submission until the field is filled.multipleon the form lets one person submit more than once. Without it, a second submission replaces their first.
Hooks: [data-smp-form="id"] on the form, [data-smp-form-field="name"] on
each field, [data-smp-form-submit] on the button, [data-smp-form-status] on
the confirmation line.
Checklists
<smp-checklist id="launch">
<smp-item value="copy">Finalize homepage copy</smp-item>
<smp-item value="deploy">Deploy to staging</smp-item>
</smp-checklist>Shared team state. When anyone ticks "Deploy to staging" it is ticked for everyone, which is what turns a launch plan into a board where the remaining work visibly shrinks.
Each <smp-item value="..."> is one to-do; value is the stable key the tick is
filed under, and the text inside is the label.
Hooks: [data-smp-checklist="id"] on the container, carrying
data-smp-checked-count and data-smp-total for a progress readout;
[data-smp-item="value"] on each item, gaining data-smp-checked when done;
[data-smp-item-label] on the item's own text (style that rather than a bare
span, so you do not also catch the vote tally); data-smp-checklist-closed
when closed.
[data-smp-item][data-smp-checked] [data-smp-item-label] { text-decoration: line-through; opacity: .55; }
[data-smp-checklist]::after { content: attr(data-smp-checked-count) " / " attr(data-smp-total) " done"; }Letting readers add and upvote
A launch plan is written up front. A todo list, a shopping list or an idea board is not: the point is that people add to it.
<smp-checklist id="ideas" allow-add allow-vote sort="votes" add-label="Add an idea">
<smp-item value="search">Better search</smp-item>
</smp-checklist>allow-addgrows an input, so anyone with access can add an item everyone then sees.add-labelsets its placeholder and button text.allow-voteputs an upvote control on each item. One vote per person per item, click again to withdraw, and you can see which ones you backed.sort="votes"orders most-backed first. Ties keep their place, so a list nobody has voted on does not shuffle.- A checklist with
allow-addand no items at all is valid, for a list that starts empty. - Adding something already on the list does nothing, rather than duplicating it. Anyone with access can remove an added item, the same way anyone can untick one.
- An item is capped at 200 characters, and one list holds up to 200 reader-added items.
Hooks: [data-smp-item-added] on a reader-added item,
[data-smp-item-remove] on its remove control, [data-smp-checklist-add] on the
form, [data-smp-item-vote] on the upvote control (carrying
data-smp-vote-count, plus data-smp-voted when you are one of the backers, and
[data-smp-vote-tally] for the number).
Anything a reader types here is public to the link
This is the opposite of a form. An item someone adds is shown to everyone who
can open the page, immediately, and it is anonymous: there is no record of who
added what, and no way for them to take it back except by removing it for
everybody. On a private page that audience is the people you invited. Think
twice before putting allow-add on a public page.
Groups: giving items a status
Declare statuses and the list renders one group per status, each item filed
under its own. That is a board.
<smp-checklist id="board" statuses="todo:To do, doing:In progress, done:Done"
allow-add allow-vote sort="votes">
<smp-item value="brief" status="done">Write the launch brief</smp-item>
</smp-checklist>- A comma-separated list of
id:Labelpairs. The first is the default, where new items land. - A reader-added item gets a chip showing its status; clicking it opens a dialog
to pick another. An authored
<smp-item status="done">sits in that group and stays there, because its status is markup. sort="votes"sorts within each group, not across the whole list.
Status is an attribute of the item, not a place it lives. Re-filing is a single write to the row already there, so an item keeps its ticks and its votes as it moves.
Hooks: [data-smp-group="id"] per group with data-smp-group-count and a
[data-smp-group-label]; data-smp-item-status on each item;
[data-smp-item-status-trigger] on the chip and [data-smp-status-dialog] on
the dialog, whose parts are [data-smp-status-dialog-title],
[data-smp-status-choice="id"] and [data-smp-status-cancel]. It is a real
<dialog>, so Escape and the backdrop work on their own.
Leave statuses off and the list renders flat. Items stored before a status
existed read as the first one, so adding statuses to a list you already use is
safe. Renaming an item does start its votes over, because the wording is the
item's identity.
Reactions
<smp-reactions id="launch-note" emoji="👍,🤔,🎉"></smp-reactions>The lightest thing a reader can do, which is the point: someone who would never write a comment will still tap an emoji. It answers the question you have every time you share a page, did anyone actually read it.
emojiis optional; leave it off for a default set. Up to 12, with duplicates and over-long entries ignored so a stray sentence cannot become a button.- One row per person per emoji, so a reader can pick several, clicking again takes one back, and the count is a headcount, not a click count. You can see which ones you picked.
Hooks: [data-smp-reactions="id"] on the row with data-smp-reactions-total
and data-smp-reactions-closed; [data-smp-reaction="👍"] on each button with
data-smp-reaction-count and data-smp-reacted; [data-smp-reaction-emoji] and
[data-smp-reaction-tally] inside.
See also
- Data and backends for
window.smp, the raw append-and-read stream behind these blocks, and for when you genuinely need your own API or database. - Comments when feedback should be attributed to a person.
- Access for who can open a page, which is also who can act on its blocks.
Images in pages
Bundle images with a page from the browser, or host them over MCP with add_page_image, and reference them by URL.
Data and backends
Where a page can store data. The built-in blocks need no backend of your own; window.smp covers custom shapes; and a page can call your own API or a hosted database over HTTP.