React + MotionMicrointeractions
Floating emoji reactions
A Dock‑style reaction bar where emojis grow as you approach, and each tap sends a few of them floating upwards.
Settings
3
Code
import { useRef, useState } from "react";
import { AnimatePresence, motion, useMotionValue, useReducedMotion, useSpring, useTransform } from "framer-motion";
import type { MotionValue } from "framer-motion";
interface Floater {
id: number;
emoji: string;
x: number;
drift: number;
rot: number;
size: number;
rise: number;
delay: number;
}
interface DockEmojiProps {
emoji: string;
mouseX: MotionValue<number>;
onReact: (emoji: string, clientX: number) => void;
}
function DockEmoji({ emoji, mouseX, onReact }: DockEmojiProps) {
const ref = useRef<HTMLButtonElement>(null);
const distance = useTransform(mouseX, (x) => {
const r = ref.current?.getBoundingClientRect();
return r ? x - (r.left + r.width / 2) : Infinity;
});
const size = useSpring(useTransform(distance, [-96, 0, 96], [40, 56, 40]), {
stiffness: 420,
damping: 30,
mass: 0.4,
});
const fontSize = useTransform(size, (s) => s * 0.56);
return (
<motion.button
ref={ref}
type="button"
aria-label={"React with " + emoji}
onClick={(e) => onReact(emoji, e.currentTarget.getBoundingClientRect().left + e.currentTarget.offsetWidth / 2)}
whileTap={{ scale: 0.8 }}
transition={{ type: "spring", stiffness: 600, damping: 20 }}
style={{ width: size, height: size, fontSize }}
className="grid shrink-0 place-items-center rounded-full leading-none outline-none focus-visible:ring-2 focus-visible:ring-black/20 dark:focus-visible:ring-white/30"
>
{emoji}
</motion.button>
);
}
interface LiveReactionsProps {
emojis?: string;
burst?: number;
}
export default function LiveReactions({
emojis = "👍 ❤️ 😂 😮 🔥",
burst = 3,
}: LiveReactionsProps) {
const list = emojis.split(/\s+/).filter(Boolean).slice(0, 6);
const [floaters, setFloaters] = useState<Floater[]>([]);
const stage = useRef<HTMLDivElement>(null);
const nextId = useRef(0);
const mouseX = useMotionValue(Infinity);
const reduce = useReducedMotion();
const react = (emoji: string, clientX: number) => {
const left = stage.current?.getBoundingClientRect().left ?? 0;
const items = Array.from({ length: burst }, (_, i) => ({
id: nextId.current++,
emoji,
x: clientX - left + (Math.random() - 0.5) * 14,
drift: (Math.random() - 0.5) * 56,
rot: (Math.random() - 0.5) * 40,
size: 22 + Math.random() * 12,
rise: 120 + Math.random() * 60,
delay: i * 0.08,
}));
setFloaters((f) => [...f, ...items].slice(-36));
};
return (
<div ref={stage} className="relative flex h-60 w-full max-w-xs items-end justify-center">
<div className="pointer-events-none absolute inset-0" aria-hidden="true">
<AnimatePresence>
{floaters.map((p) => (
<motion.span
key={p.id}
className="absolute bottom-14 select-none leading-none"
style={{ left: p.x, fontSize: p.size, marginLeft: -p.size / 2 }}
initial={{ y: 0, opacity: 0, scale: 0.4 }}
animate={{
y: reduce ? -40 : -p.rise,
x: reduce ? 0 : [0, p.drift * 0.5, -p.drift * 0.3, p.drift],
rotate: reduce ? 0 : [0, p.rot, -p.rot * 0.5, p.rot * 0.3],
opacity: [0, 1, 1, 0],
scale: [0.4, 1.1, 1, 0.9],
}}
transition={{ duration: 1.8, delay: p.delay, ease: [0.22, 1, 0.36, 1] }}
onAnimationComplete={() => setFloaters((f) => f.filter((q) => q.id !== p.id))}
>
{p.emoji}
</motion.span>
))}
</AnimatePresence>
</div>
<div
onPointerMove={(e) => mouseX.set(e.clientX)}
onPointerDown={(e) => mouseX.set(e.clientX)}
onPointerLeave={() => mouseX.set(Infinity)}
className="relative flex items-end gap-1 rounded-full bg-white/85 p-1.5 shadow-[0_0_0_1px_rgba(0,0,0,0.05),0_1px_2px_rgba(0,0,0,0.05),0_12px_32px_-8px_rgba(0,0,0,0.16)] backdrop-blur-xl dark:bg-[#2c2c2e]/85 dark:shadow-[0_0_0_1px_rgba(255,255,255,0.08),0_12px_32px_-8px_rgba(0,0,0,0.6)]"
>
{list.map((emoji, i) => (
<DockEmoji key={emoji + i} emoji={emoji} mouseX={mouseX} onReact={react} />
))}
</div>
</div>
);
}
Prompt for AI
Paste it into ChatGPT, Claude or Cursor and it will adapt the block to your project with these settings, even if you don’t use React.
More in Microinteractions
Where to use it
Floating emoji reactions give a live moment some noise: a livestream, a webinar, a virtual event, even the end of a blog post. The bar magnifies like the macOS Dock under the cursor, and every tap releases one to six emojis that drift and spin as they rise, like a real crowd.
The magnification needs a mouse; on phones people just tap and the emojis float up as usual. Stick to five or six reactions. Emojis look a bit different on every system, so check the set on both an iPhone and an Android device.