Vanilla Breeze

poll-wc

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.

Overview

<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.

When to use which primitive

Use thisWhen
<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.

Author surface

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.

Multi-choice

Set data-multi to allow voting for multiple options. ARIA shifts from radiogroup + radio to group + checkbox.

Closed state

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.

Wiring the backend

Single vs multi event semantics

Single-choice mode handles option switching atomically:

  • Clicking your current selection again → poll-wc:vote with action: "remove" (un-vote).
  • Clicking a different option → first emits 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:

  • Clicking an option toggles its own state. No cross-option events.

Accessibility

  • Single-choice: options live in a role="radiogroup" with role="radio" + aria-checked on each option.
  • Multi-choice: options live in a role="group" with role="checkbox" + aria-checked on each option.
  • Question: when present, the rendered question element is aria-labelledby to the option group.
  • Total readout: the total-vote count lives in an <output> with aria-live="polite", so screen readers announce it on changes.
  • Per-option label: derived aria-label like Next Monday, 33 votes (60.0%), your choice.
  • Closed: options get aria-disabled="true"; focus + keyboard nav still work for browsing.
  • Keyboard: Arrow keys move focus between options (single-choice uses roving tabindex; multi-choice keeps every option tabbable). Home / End jump to first / last. Enter / Space activates.

Attributes

AttributeTypeDefaultDescription
aria-labelstringquestion text or PollGroup label.
data-multibooleanfalseAllow multiple selections.
data-closedbooleanfalseRead-only — show results, no voting.
data-hide-countsbooleanfalseSuppress the numeric count beside each bar.
disabledbooleanfalseDisables the entire poll.

Per-option attributes (on <button data-option>)

AttributeDescription
data-optionStable identifier sent in events (required).
data-countCurrent vote count.
data-mineCurrent user voted for this option.

Events

EventBubblesDetail
poll-wc:voteyes{ option, action: 'add'|'remove', count, mine }
poll-wc:closed-changeyes{ closed }

JavaScript API

MemberDescription
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.
totalVotesSum of all option counts.
myVotesArray of option ids the current user voted for.

See also