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.
GitHub-style emoji reaction picker. Persistent in-flow bar of reaction chips with counts + a trigger that opens a curated palette popover.
<reaction-bar> is a persistent bar of emoji-reaction chips attached to an item (a comment, a post, a message). A single auto-rendered trigger opens a curated palette popover. The component is presentational: authors own the source of truth (counts, per-user state, persistence) and hook the reaction-bar:toggle event to make the backend call.
Three adjacent VB primitives sit nearby. The trigger model is the tell — pick the one whose trigger matches your interaction.
| Use this | When |
|---|---|
<reaction-bar> | Persistent in-flow bar of curated emoji reactions with counts + own-reaction toggle, attached to an item (comment / post / message). |
<selection-menu> | Contextual floating toolbar that appears on text selection (anchored to a Range). |
<emoji-picker> | The user might pick any emoji from the full Unicode catalog (search, categories, recent). |
<star-rating> | Single-value 1–N rating submitted with a form. |
Two slots:
<button data-reaction data-count [data-mine]> children. The component decorates them with the count, aria-pressed, and a derived aria-label.<template data-palette> child. The component clones the buttons into a <pop-over> when the trigger opens.The trigger button (😀+) is auto-rendered at the end of the bar — you do not author it.
<reaction-bar aria-label="Reactions to comment 482"> <button data-reaction="thumbsup" data-count="3" data-mine>👍</button> <button data-reaction="rocket" data-count="2">🚀</button> <template data-palette> <button data-reaction="thumbsup">👍</button> <button data-reaction="thumbsdown">👎</button> <button data-reaction="laugh">😄</button> <button data-reaction="hooray">🎉</button> <button data-reaction="confused">😕</button> <button data-reaction="heart">❤️</button> <button data-reaction="rocket">🚀</button> <button data-reaction="eyes">👀</button> </template></reaction-bar>
The component does not track state. Authors own counts + per-user state + persistence. The flow:
reaction-bar:toggle fires with { reaction, action, count, mine }.bar.setCount(reaction, count, { mine }).bar.addEventListener('reaction-bar:toggle', async (e) => { const { reaction, action, count, mine } = e.detail; const res = await fetch(`/reactions/${reaction}`, { method: action === 'add' ? 'POST' : 'DELETE', }); const { count: newCount } = await res.json(); bar.setCount(reaction, newCount, { mine: action === 'add' });});
Set data-disabled for archived threads or other read-only contexts. Chips don't toggle; the trigger is disabled.
<reaction-bar data-disabled aria-label="Reactions"> <button data-reaction="thumbsup" data-count="12">👍</button> <button data-reaction="heart" data-count="3">❤️</button> <template data-palette> <button data-reaction="thumbsup">👍</button> <button data-reaction="heart">❤️</button> </template></reaction-bar>
role="toolbar" with aria-label="Reactions" (override via aria-label on the host).role="button", aria-pressed reflects data-mine. Each chip gets a derived aria-label like 👍, 3 reactions, you reactedso AT users hear count + own-state in one announcement.
aria-haspopup="dialog", aria-expanded tracks open state.role="dialog" rendered by <pop-over> in the top layer; Escape closes (handled by pop-over).| Attribute | Type | Default | Description |
|---|---|---|---|
aria-label | string | Reactions | Toolbar label. |
data-trigger-icon | string | 😀 | Text/emoji content of the trigger button. |
data-trigger-label | string | Add reaction | aria-label for the trigger. |
data-disabled | boolean | false | Read-only mode. |
| Attribute | Type | Description |
|---|---|---|
data-reaction | string | Stable identifier sent to the backend (e.g. thumbsup, heart). |
data-count | number | Current count (≥ 1 to show; 0 removes the chip). |
data-mine | boolean | Whether the current user has this reaction. |
| Event | Bubbles | Detail |
|---|---|---|
reaction-bar:toggle | yes | { reaction, action: 'add'|'remove', count, mine } |
reaction-bar:palette-open | yes | — |
reaction-bar:palette-close | yes | — |
| Method | Description |
|---|---|
setCount(reaction, count, { mine }) | Update a chip after the server confirms. Creates the chip from the palette template if it doesn't exist yet; removes the chip when count drops to 0. |
openPalette() | Open the palette popover programmatically. |
closePalette() | Close it. |
<selection-menu> — floating toolbar that anchors to text selection.<emoji-picker> — full Unicode emoji browser with search + categories.<star-rating> — single-value form-associated rating.<pop-over> — the surface this component composes for the palette.