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
Active State
Highlight a reaction the current user has selected:
Zero Count (Hidden)
When count is zero, only the emoji is shown:
With User List
Provide a list of users for screen reader context:
With Livewire Integration
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.
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 activationaria-pressed="true|false"reflects active statearia-labelcombines emoji with reaction count (e.g. "👍, 5 people reacted")- When
usersis provided, asr-onlyspan witharia-describedbylists all names
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Move focus to the button |
Enter / Space |
Activate the button |
Pitfalls
- Don't bind
wire:click.liveon every reaction button. Each click round-trips to the server with full component state. The component's:on-reactcallback 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 |