Like / Reaction Button
FreeReaction button with 4 icon variants (heart, thumbs-up, star, clap), burst particle animation on toggle, and an optimistic count that rolls back when the write fails. 3 sizes.
</>sv-like likereactionbutton
Complexity tier: Interactive The primitive bar, plus declared async states, a keyboard model, focus restoration and undo for destructive actions.
import { SvLikeComponent } from '@styloviz/forms';Demo & usage
Basic usage
Live, interactive instance.
When to use
- Adding a lightweight reaction/like affordance to posts, comments or cards.
- You want an optimistic count with a satisfying burst animation on toggle.
- For multi-emoji reactions on a thread, use the Comment component's reactions.
Async states
- error
Server writes
- Writes
- likeChange
Each payload carries a
requestKey— send it with the request so a retry cannot apply the same change twice.- Rollback
- Call
event.rollback()when the save fails — the change is already on screen, and this is what puts it back. - While saving
- Bind
pending— with two writes outstanding there is no single state to roll back to, so the action stays locked until the first one lands. - On failure
- Pass the failure to
errorto show it in place — without taking away what the user was working on.
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| count | number (two-way) | 0 | The reaction count (two-way). A `model`, because the count is the thing the toggle changes: it moves the instant the button is clicked, and the host's next value — the number the server actually recorded — wins over whatever was shown optimistically. |
| liked | boolean (two-way) | false | Whether the current user has reacted (two-way: `[(liked)]`). Bind it two-way, not one-way. The button flips the state itself, and being a `model` is what pushes that back into YOUR signal, so the two never drift and a later correction is always a real change. It used to be a plain input mirrored into a `linkedSignal`, which only re-runs when its source **changes** — so a host undoing a rejected like by re-asserting `[liked]="false"` set it to the value it already held, nothing re-ran, and the like stayed on screen permanently. A one-way `[liked]` has the same flaw for the same reason: Angular skips a binding whose expression has not changed. |
| variant | LikeVariant | 'heart' | Icon variant. |
| size | LikeSize | 'md' | Size. |
| showCount | boolean | true | Show the count next to the icon. |
| disabled | boolean | false | Disabled state. |
| pending | boolean | false | True while the toggle is being written. The button stops responding, which is what keeps `rollback()` unambiguous — and it is also the answer to a double-tap producing two requests. |
| error | SvDataError | string | null | null | The last write failed. Rendered as a danger ring, a triangle glyph and the message as visible text under the button — tied to it with `aria-describedby` — so the state is not conveyed by colour alone. Roll the value back as well — this input reports the failure, it does not undo it. |
| signedIn | boolean | true | Whether the viewer is signed in. When false the button does not toggle; it emits `authRequired` so the host can prompt for sign-in. A reaction control shown to a signed-out user had no defined behaviour: it toggled, looked saved, and the write it reported could not succeed. |
Outputs
| Event | Payload type | Description |
|---|---|---|
| likeChange | LikeToggleEvent | The user toggled the reaction. |
| authRequired | void | A signed-out viewer tried to react. |
| countChange | number | Emits the new `count` whenever it changes — the output half of `[(count)]`. |
| likedChange | boolean | Emits the new `liked` whenever it changes — the output half of `[(liked)]`. |
Customization & theming
- Switch icon with `variant`: `heart`, `thumbs-up`, `star` or `clap`.
- Control size with `size` (sm, md, lg).
- Hide the counter by setting `[showCount]="false"`.
- Seed initial state with `[liked]="true"`; bind both `[(liked)]` and `[(count)]` two-way so the server's numbers win over the optimistic guess.
- `[error]` accepts a string or an `SvDataError` — the message renders as visible "Not saved: …" text under the button, announced through a polite live region and linked to the button with `aria-describedby`.
Best practices
- Do: Call `rollback()` from the `likeChange` payload when the write is rejected — the button has already moved, and nothing else puts it back.
- Do: Send `requestKey` with the write and ignore a key you have already applied, so a retry cannot count twice.
- Do: Bind `[pending]` while the write is in flight. It stops a double-tap sending two conflicting requests, and it is what keeps `rollback()` unambiguous.
- Do: Seed `[liked]` and `[count]` from the current user's state on load.
- Don't: Do not leave a reaction button interactive for a signed-out viewer. Bind `[signedIn]="false"` and handle `(authRequired)`, or the toggle looks saved and cannot be.
- Tip: Pick a `variant` (heart/thumbs-up/star/clap) that matches the content's tone.
Accessibility
- A native `<button>` with `aria-pressed` reflecting the toggled state, and an accessible name that carries the count and "not saved" after a failed write.
- A failed write is a danger ring, a triangle glyph and visible message text — the button's `aria-describedby` points at that text, so the reason is read once as its description — never colour alone.
- While `[pending]`, the button sets `aria-busy` and stops responding.
- The burst is `motion-safe:` only and is not rendered at all under `prefers-reduced-motion` (with `motion-reduce:hidden` covering a preference that changes mid-burst), so reduced motion gets no particles rather than four dots parked on the button.
- Disabled state sets `aria-disabled` and blocks interaction.
Feedback on Like / Reaction Button
Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.