Componentmedia0.82 KB gzipZero dependencies

ReactionBar

Emoji buttons with counts, active reaction pops, and hover tooltips.

Preview

Live Preview

ReactionBar

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add reaction-bar

Or install the full npm package:

npm install bigbullui
import { ReactionBar } from "@/components/ui/reaction-bar";

<ReactionBar reactions={reactions} onReact={onReact} />

Usage guidance

Use ReactionBar when a ticket stub surface needs Emoji buttons with counts, active reaction pops, and hover tooltips. Keep props minimal and prefer composition with sibling stubs.

Avoid ReactionBar for long form flows or dense data grids where a dedicated layout block fits better.

Source code

reaction-bar.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export interface Reaction {
  emoji: string;
  count: number;
  active?: boolean;
}

export interface ReactionBarProps {
  reactions: Reaction[];
  onReact?: (emoji: string) => void;
}

const ReactionBar: React.FC<ReactionBarProps> = ({ reactions, onReact }) => {
  return (
    <div className="flex flex-wrap gap-2" role="group" aria-label="Reactions">
      {reactions.map((reaction) => (
        <button
          key={reaction.emoji}
          type="button"
          onClick={() => onReact?.(reaction.emoji)}
          aria-pressed={reaction.active}
          aria-label={`${reaction.emoji} reaction, ${reaction.count} votes`}
          className={cn(
            "relative flex-0 flex cursor-pointer flex-col items-center gap-1 rounded-md border border-border/30 px-3 py-1 transition-colors duration-200 hover:bg-accent/5 hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none",
            reaction.active && "bg-accent/20 text-accent-foreground"
          )}
        >
          <span
            className={cn(
              "text-lg",
              "motion-reduce:animate-none"
            )}
            aria-hidden="true"
          >
            {reaction.emoji}
          </span>
          <span
            className={cn(
              "text-[10px] mono uppercase text-muted-foreground",
              "motion-reduce:animate-none"
            )}
            aria-hidden="true"
          >
            {reaction.count}
          </span>

          {/* Active pop animation */}
          {reaction.active && (
            <span
              aria-hidden="true"
              className={cn(
                "absolute top-0 right-0 h-3 w-3 rounded-full bg-accent/30 animate-[scale_0.3s_ease-out] opacity-0"
              )}
            />
          )}

          {/* Hover tooltip */}
          <span
            aria-hidden="true"
            className="absolute -bottom-2 left-1/2 -translate-x-1/2 text-[8px] mono uppercase text-muted-foreground opacity-0 transition-opacity duration-150 group-hover:opacity-100"
          >
            {reaction.emoji} reaction
          </span>
        </button>
      ))}
    </div>
  );
};

export { ReactionBar };

Props

PropTypeDescription
reactionsReaction[]
onReact?(emoji: string) => void