ReactionEmojiPicker
freeReactionEmojiPicker floats a compact emoji grid from a trigger so users can add reactions without leaving the current context.
@floating-ui/reactreact-icons
Preview
Dark
Click the smiley to pick a reaction
import { useState } from 'react';
import ReactionEmojiPicker from '@/components/composites/ReactionEmojiPicker';
export default function UsageDemo() {
const [picked, setPicked] = useState<string | null>(null);
return (
<div className="flex flex-col items-center gap-4">
<ReactionEmojiPicker onSelect={setPicked} />
<p className="text-sm text-muted-foreground">
{picked ? (
<>Selected: <span className="text-base">{picked}</span></>
) : (
'Click the smiley to pick a reaction'
)}
</p>
</div>
);
}Installation
Add this component with the NateUI CLI.
npx nateui@latest add ReactionEmojiPickerExamples
Custom Trigger
Preview
Dark
import { useState } from 'react';
import Button from '@/components/ui/Button';
import ReactionEmojiPicker from '@/components/composites/ReactionEmojiPicker';
import { PiSmiley } from 'react-icons/pi'
export default function CustomTriggerDemo() {
const [picked, setPicked] = useState<string | null>(null);
return (
<ReactionEmojiPicker
trigger={
<Button variant="default" icon={<PiSmiley />}>
Add reaction
</Button>
}
onSelect={setPicked}
/>
);
}Custom Emojis
Preview
Dark
import { useState } from 'react';
import ReactionEmojiPicker from '@/components/composites/ReactionEmojiPicker';
const FOOD_EMOJIS = [
'🍕', '🍔', '🌮', '🍣', '🍜',
'🍩', '🧁', '🍦', '🍎', '🥑',
] as const;
export default function CustomEmojisDemo() {
const [picked, setPicked] = useState<string | null>(null);
return (
<ReactionEmojiPicker
emojis={FOOD_EMOJIS}
onSelect={setPicked}
/>
);
}Placement
Preview
Dark
import Button from '@/components/ui/Button';
import ReactionEmojiPicker from '@/components/composites/ReactionEmojiPicker';
const placements = ['top', 'bottom', 'right'] as const;
export default function PlacementDemo() {
return (
<div className="flex items-center justify-center gap-4">
{placements.map((placement) => (
<ReactionEmojiPicker
key={placement}
placement={placement}
trigger={
<Button type="button">
{placement}
</Button>
}
/>
))}
</div>
);
}Reaction Bar
Preview
Dark
import { useState } from 'react';
import ReactionEmojiPicker from '@/components/composites/ReactionEmojiPicker';
type Reaction = { emoji: string; count: number; reacted: boolean };
const INITIAL: Reaction[] = [
{ emoji: '👍', count: 4, reacted: false },
{ emoji: '❤️', count: 2, reacted: true },
];
export default function ReactionBarDemo() {
const [reactions, setReactions] = useState<Reaction[]>(INITIAL);
const toggle = (emoji: string) => {
setReactions((prev) => {
const current = prev.find((r) => r.reacted);
const target = prev.find((r) => r.emoji === emoji);
if (current?.emoji === emoji) {
return prev.map((r) =>
r.emoji === emoji
? { ...r, count: r.count - 1, reacted: false }
: r,
);
}
const next = prev.map((r) => {
if (r.emoji === current?.emoji) {
return { ...r, count: r.count - 1, reacted: false };
}
if (r.emoji === emoji) {
return { ...r, count: r.count + 1, reacted: true };
}
return r;
});
if (!target) {
return [...next, { emoji, count: 1, reacted: true }];
}
return next;
});
};
const visible = reactions.filter((r) => r.count > 0);
return (
<div className="flex flex-wrap items-center gap-2">
{visible.map((r) => (
<button
key={r.emoji}
type="button"
onClick={() => toggle(r.emoji)}
className={[
'flex items-center gap-1 rounded-full border px-2 py-0.5 text-sm',
r.reacted
? 'border-primary bg-primary-soft text-primary'
: 'border-border hover:bg-accent',
].join(' ')}
>
<span>{r.emoji}</span>
<span className="font-medium tabular-nums">{r.count}</span>
</button>
))}
<ReactionEmojiPicker onSelect={toggle} placement="top" />
</div>
);
}API
| Prop | Description | Type | Default |
|---|---|---|---|
emojis | The emoji set rendered in the picker grid. | readonly string[] | ['👍','❤️','😂','😮','😢','😡','👏','🎉'] |
onSelect | Called with the chosen emoji string. The picker closes immediately after. | (emoji: string) => void | — |
trigger | Custom trigger element. Defaults to a smiley icon button. | ReactNode | — |
placement | Popover panel placement relative to the trigger. | PopoverPlacement | 'top' |
width | Pixel width of the popover panel. | number | 250 |
Types
type PopoverPlacement =
| 'top' | 'top-start' | 'top-end'
| 'bottom' | 'bottom-start' | 'bottom-end'
| 'left' | 'left-start' | 'left-end'
| 'right' | 'right-start' | 'right-end'