Skip to main content
WireKit
Copy for LLM

Reaction

An emoji reaction button for chat messages, comments, and social interactions. Supports active/inactive toggle state, count display, and screen-reader-friendly user lists.

Basic Usage

Simple reaction

Active State

Highlight a reaction the current user has selected:

Active vs inactive

Zero Count (Hidden)

When count is zero, only the emoji is shown:

Zero count reaction

With User List

Provide a list of users for screen reader context:

Reaction with user attribution

With Livewire Integration

Livewire toggle example

Optimistic UI

Pass the name of the Livewire method this component should call and the change appears immediately, then confirms or undoes itself when the server answers:

<x-wirekit::reaction
    emoji="👍"
    :count="$count"
    :active="$reacted"
    optimistic="react"
/>

Load wirekit-optimistic.js alongside whichever bundle you already use — it is a separate file so applications that do not use it pay nothing for it:

@wirekitScripts
<script src="{{ asset('vendor/wirekit/wirekit-optimistic.js') }}"></script>

Try it

The demo below runs the real path: the change shows immediately, the outline says it is provisional, and the server's answer either confirms it silently or takes it back.

Optimistic reaction — accepted, refused, and a slow answer

Accepted

The count is kept and nothing is said.

Refused

The reaction is taken back, and said out loud.

Slow to answer

The dashed outline is the provisional state.

The <livewire:demos.…> wrapper above exists only on this site — it supplies the demo methods so the page can show a real round trip. The block under it is what you write.

Only one thing is state here: whether you reacted. The count is derived from it, so a rollback puts both back together and they cannot end up disagreeing — a button that says you reacted next to a total that does not include you.

The accessible name is built in the browser for the same reason. Built on the server it would name the count the server saw, so an optimistic change would show six and announce five.

What a screen reader hears is the same for every component that supports this, and it is written out in full — one hedged announcement at the change, silence on confirmation, one more only if the server refuses, and focus that never moves — in Optimistic UI — the announcement contract. That page is also where the boundary is stated: this covers mutations, not sorting, filtering or pagination.

Props

Prop Type Default Description
optimistic string|null null Livewire method to call, showing your reaction before the server confirms it. See Optimistic UI.
optimisticArgs array [] Extra arguments appended to the optimistic action call, after the new value — the row this control belongs to.
emoji string null The emoji character to display
count int 0 Number of reactions
active bool false Whether the current user has reacted
users array [] List of user names (for screen readers)

Accessibility

  • <button type="button"> for keyboard activation
  • aria-pressed="true|false" reflects active state
  • aria-label combines emoji with reaction count (e.g. "👍, 5 people reacted")
  • When users is provided, a sr-only span with aria-describedby lists all names

Keyboard Interaction

Key Action
Tab Move focus to the button
Enter / Space Activate the button

Pitfalls

  • Don't bind wire:click.live on every reaction button. Each click round-trips to the server with full component state. The component's :on-react callback prop debounces; use it.

Design Tokens

Token Used for
--font-wk-sans Button font family
--font-wk-heading-weight Count weight
--text-wk-sm Count font size
--color-wk-text-muted Inactive count text
--color-wk-accent / --color-wk-accent-content Active border + count text
--color-wk-bg / --color-wk-bg-elevated / --color-wk-bg-muted Background per active / hover state
--color-wk-border Inactive border
--border-wk-width Border width
--radius-wk-full Pill border radius
--transition-wk-duration / --transition-wk-easing Hover / toggle transition

Was this page helpful?

Voting requires cookies or local storage. What we store