Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Voting + live results with single/multi-choice and closed state. Per-option counts and own-vote flag; presentational, mirrors the reaction-bar author-owned-state pattern.
<poll-wc> is a voting + live-results primitive. Authors render options as <button data-option data-count [data-mine]> children with an optional <span slot="question">. The component renders the question, the option list with result bars, and a live total-vote count.
Like <reaction-bar>, the component is presentational: it emits poll-wc:vote and lets authors make the backend call. After the server confirms, authors call setCount(option, count, { mine }) to update the chip.
| Use this | When |
|---|---|
<poll-wc> | Question + labeled voting options + live result bars + per-user vote flag. |
<reaction-bar> | Many small emoji icons toggled per-user — no question, no result chart. |
<star-rating> | Single 1–N rating, form-associated. Polls have arbitrary labelled options and don't submit with a form. |
<form> + <input type="radio"> | Plain form input with no live results. |
<chart-wc> | Renders charts from data. <poll-wc> combines voting with result visualization in one widget. |
<poll-wc> <span slot="question">When should we ship?</span> <button data-option="friday" data-count="12">Friday</button> <button data-option="next-mon" data-count="33" data-mine>Next Monday</button> <button data-option="next-fri" data-count="8">Next Friday</button> <button data-option="never" data-count="2">Never</button></poll-wc>
Each option carries a stable data-option id (sent in events), the current vote data-count, and an optional data-mine flag for the current user's vote. The button text is the visible label.
Set data-multi to allow voting for multiple options. ARIA shifts from radiogroup + radio to group + checkbox.
<poll-wc data-multi> <span slot="question">Which features matter to you? (pick any)</span> <button data-option="dark" data-count="80" data-mine>Dark mode</button> <button data-option="i18n" data-count="55">i18n</button> <button data-option="offline" data-count="40" data-mine>Offline</button> <button data-option="oauth" data-count="30">OAuth</button></poll-wc>
Set data-closed for results-only display (no voting). Use poll.close() / poll.open() to toggle programmatically when a deadline passes; the component fires poll-wc:closed-change.
<poll-wc data-closed> <span slot="question">Best win this quarter?</span> <button data-option="ship" data-count="22">Shipping VB 1.0</button> <button data-option="onboard" data-count="9">Onboarding revamp</button> <button data-option="docs" data-count="14">Docs rewrite</button></poll-wc>
const poll = document.querySelector('poll-wc'); poll.addEventListener('poll-wc:vote', async (e) => { const { option, action, count, mine } = e.detail; // Single-choice: switching to a new option emits a `remove` for the // previously-selected option BEFORE the `add`. Both events route through // here in order, so authors can apply both updates idempotently. const res = await fetch(`/polls/123/votes/${option}`, { method: action === 'add' ? 'POST' : 'DELETE', }); const { count: newCount } = await res.json(); poll.setCount(option, newCount, { mine: action === 'add' });});
Single-choice mode handles option switching atomically:
poll-wc:vote with action: "remove" (un-vote).action: "remove" for your previous choice, then action: "add" for the new one. Two events fire in that order.Multi-choice mode treats each option independently:
role="radiogroup" with role="radio" + aria-checked on each option.role="group" with role="checkbox" + aria-checked on each option.aria-labelledby to the option group.<output> with aria-live="polite", so screen readers announce it on changes.aria-label like Next Monday, 33 votes (60.0%), your choice.
aria-disabled="true"; focus + keyboard nav still work for browsing.| Attribute | Type | Default | Description |
|---|---|---|---|
aria-label | string | question text or Poll | Group label. |
data-multi | boolean | false | Allow multiple selections. |
data-closed | boolean | false | Read-only — show results, no voting. |
data-hide-counts | boolean | false | Suppress the numeric count beside each bar. |
disabled | boolean | false | Disables the entire poll. |
<button data-option>)| Attribute | Description |
|---|---|
data-option | Stable identifier sent in events (required). |
data-count | Current vote count. |
data-mine | Current user voted for this option. |
| Event | Bubbles | Detail |
|---|---|---|
poll-wc:vote | yes | { option, action: 'add'|'remove', count, mine } |
poll-wc:closed-change | yes | { closed } |
| Member | Description |
|---|---|
setCount(option, count, { mine }) | Update a single option after the server confirms. |
setCounts(counts, { mine }) | Bulk-update from a server snapshot. mine can be a string (single-choice) or string[] (multi). |
open() / close() | Toggle data-closed programmatically. |
totalVotes | Sum of all option counts. |
myVotes | Array of option ids the current user voted for. |
<reaction-bar> — many small emoji reactions; same author-owned-state pattern.<star-rating> — single-value form-associated rating.