caliper uidocs

Reaction bar

Free

Emoji reactions under a message or post. Each chip is a toggle; the count rolls and your chip settles with one small pop. The picker adds a new reaction.

Installs components/ui/reaction-bar.tsxDepends on @radix-ui/react-popover, lucide-reactMotion none in component source
pnpm dlx shadcn@latest add https://caliperui.site/r/reaction-bar.json

Maya Okafor shipped invoice reminders to every workspace.

When to use #

Use it for

  • Comments, chat messages, changelog entries.
  • Light feedback where a reply would be too much.

Skip it when

  • Votes that rank items; use the vote control.
  • Ratings with a scale; use the rating.

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/reaction-bar.json

Adds its 4 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

This is the code behind the preview above. It is built from toggleReaction and ReactionBar.

example.tsx
import { Rocket } from "lucide-react"
import { useState } from "react"
import { ReactionBar, type Reaction } from "@/components/ui/reaction-bar"

export function ReactionBarDemo() {
  const [reactions, setReactions] = useState<Reaction[]>([
    { emoji: "🎉", label: "Party", count: 12, mine: true },
    { emoji: "👀", label: "Eyes", count: 4 },
    { emoji: "🚀", label: "Rocket", count: 1280 },
  ]);
  return (
    <div className="grid w-full max-w-md gap-3 rounded-surface border border-border bg-surface p-4">
      <p className="text-sm"><span className="font-medium">Maya Okafor</span> <span className="text-muted-foreground">shipped</span> invoice reminders to every workspace.</p>
      <ReactionBar reactions={reactions} onChange={setReactions} />
    </div>
  );
}

API reference #

toggleReaction

component

No props of its own; it accepts the native props of its root element.

ReactionBar

component
PropTypeDefault
reactions *readonly Reaction[]—
onChange *(next: Reaction[]) => void—
choicesreadonly { emoji: string; label: string; }[][ { emoji: "👍", label: "Thumbs up" }, { emoji: "🎉", label: "Party" }, { emoji: "❤️", label: "Heart" }, { emoji: "👀", label: "Eyes" }, { emoji: "🚀", label: "Rocket" }, { emoji: "😄", label: "Smile" }, { emoji: "🙏", label: "Thanks" }, { emoji: "✅", label: "Done" }, ]
classNamestring—

Accessibility #

It exposes group semantics, with aria-label, aria-pressed and aria-hidden kept in step with state.

Keyboard and focus handling comes from Radix popover: arrow keys move through items, Enter and Space select, and Escape closes and returns focus.

Motion #

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.