Vanilla Breeze

reaction-bar

GitHub-style emoji reaction picker. Persistent in-flow bar of reaction chips with counts + a trigger that opens a curated palette popover.

Overview

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

When to use which primitive

Three adjacent VB primitives sit nearby. The trigger model is the tell — pick the one whose trigger matches your interaction.

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

Author surface

Two slots:

  1. Existing chips — direct <button data-reaction data-count [data-mine]> children. The component decorates them with the count, aria-pressed, and a derived aria-label.
  2. Available reactions — inside a <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.

State ownership

The component does not track state. Authors own counts + per-user state + persistence. The flow:

  1. User clicks a chip or a palette option.
  2. reaction-bar:toggle fires with { reaction, action, count, mine }.
  3. Author makes the backend call.
  4. Author updates the bar via bar.setCount(reaction, count, { mine }).

Read-only mode

Set data-disabled for archived threads or other read-only contexts. Chips don't toggle; the trigger is disabled.

Accessibility

  • Bar: role="toolbar" with aria-label="Reactions" (override via aria-label on the host).
  • Chips: role="button", aria-pressed reflects data-mine. Each chip gets a derived aria-label like 👍, 3 reactions, you reacted so AT users hear count + own-state in one announcement.
  • Trigger: aria-haspopup="dialog", aria-expanded tracks open state.
  • Palette: role="dialog" rendered by <pop-over> in the top layer; Escape closes (handled by pop-over).
  • Keyboard nav on the bar: Arrow Left / Right cycle between chips and trigger (roving tabindex); Home / End jump to ends; Enter / Space activate.
  • Keyboard nav in the palette: Arrow keys cycle through options; Home / End jump to ends; Enter / Space select.
  • Focus return: after the palette closes, focus returns to the trigger.

Attributes

AttributeTypeDefaultDescription
aria-labelstringReactionsToolbar label.
data-trigger-iconstring😀Text/emoji content of the trigger button.
data-trigger-labelstringAdd reactionaria-label for the trigger.
data-disabledbooleanfalseRead-only mode.

Chip attributes

AttributeTypeDescription
data-reactionstringStable identifier sent to the backend (e.g. thumbsup, heart).
data-countnumberCurrent count (≥ 1 to show; 0 removes the chip).
data-minebooleanWhether the current user has this reaction.

Events

EventBubblesDetail
reaction-bar:toggleyes{ reaction, action: 'add'|'remove', count, mine }
reaction-bar:palette-openyes
reaction-bar:palette-closeyes

JavaScript API

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

See also

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