Run the following command. New project? Run npx shadcn@latest init first to set up Tailwind and path aliases.
npx shadcn@latest add @aicanvas/jar-of-emotions
2
For dark mode, add the dark class to your <html> element:
Optional
<html class="dark">
Install with AI Canvas MCP
With AI Canvas MCP, your AI knows every component we ship. Ask for one inside Claude Code, Codex, or Cursor and it installs the component you pick. Works with any AI Canvas account, free or premium.
Emoji Reaction Jar is a glass jar widget with a lid that springs open when you tap one of the reaction buttons below it, sending the matching reaction emoji bouncing down through the jar. The bounce and pile-up are real Matter.js physics, so multiple emojis stack and jostle each other as you keep clicking. Motion drives the lid hinge and the spawning animation. It is a delightful drop-in for feedback widgets, end-of-article reaction bars, and empty states that want users to leave a mark.
Built with
MotionMatter.jsTailwind CSS
Frequently asked questions
Is Emoji Reaction Jar free to use?
Yes. Emoji Reaction Jar is part of the free AI Canvas library and is open source under the MIT license, so you can use it in personal and commercial projects.
How do I install Emoji Reaction Jar?
One command: npx shadcn@latest add https://aicanvas.me/r/jar-of-emotions.json, free with a free AI Canvas account. Or connect the AI Canvas MCP server and ask your AI editor to install Emoji Reaction Jar for you. You can also copy the source straight from the Code tab, no account needed. It works in any React project with Tailwind CSS.
What is Emoji Reaction Jar built with?
Emoji Reaction Jar is built with React and TypeScript, using Motion, Matter.js, and Tailwind CSS. It ships with both light and dark styling.
Where would I use Emoji Reaction Jar?
Common uses include Reactions, Feedback, and Empty state. Like every AI Canvas component, it is self-contained and drops into any React project.
Can I remix Emoji Reaction Jar with AI?
Yes. Emoji Reaction Jar ships with one comprehensive AI prompt written against the real source code. Open "Remix with AI" on this page to read and copy it into Claude, Cursor, ChatGPT, or any AI tool. Prompts are for remixing your own variation; for the exact component, install it with the one-command CLI.
One comprehensive prompt, written against the real source code. Works in Claude, Cursor, ChatGPT, or any AI tool you use.
This prompt is for remixing. Use it to build your own variation of Emoji Reaction Jar. Results depend on the model you use, and no prompt in the world is 100% exact.
Want the exact component?
One command installs it, pixel-perfect. Copy, paste into your project, done.
npx shadcn@latest add @aicanvas/jar-of-emotions
AI prompt for Emoji Reaction Jar
Build a standalone React component called JarOfEmotions in a single file.
Stack: Next.js App Router, TypeScript strict, Tailwind CSS v4, Framer Motion. No shadcn, no extra UI libs. Verify these are set up before writing code.
Install: npm install matter-js framer-motion && npm install -D @types/matter-js
---
## Overview
A physics-based reaction widget called "Emoji Reaction Jar". A centered SVG glass jar holds emoji reactions inside. Clicking a reaction button springs the lid open and a matching emoji flies up out of the jar, then bounces down onto the clicked button. Emojis accumulate on the button row with real 2D physics.
---
## Layout (top β bottom, vertically centered on screen)
1. SVG jar (largest element)
2. Title "Emoji Reaction Jar" + tagline "Reactions on tap" (below the jar)
3. Row of 6 reaction buttons
Container: flex-col, justify-center, gap-7. Root: min-h-screen, bg-sand-100 dark:bg-sand-950 (or equivalent neutral dark/light surfaces).
---
## Emotion buttons
Six buttons in a horizontal wrap row, each with an emoji above a short label:
π₯ Banger | π Perfect | π Pure joy | π Crying | π§βπ³ Cooking | π GOAT
Button style: rounded pill, subtle border, dark glass fill. All labels use the same muted light color (not per-emotion color). Gap between emoji and label: gap-2. Framer Motion whileHover y:-2, whileTap scale:0.94.
---
## SVG jar
ViewBox 0 0 200 240. Scaled to min(62vw, 280px) wide, aspect-ratio preserved.
**Jar body path (closed, glass fill):**
M 28 44 Q 22 120, 30 218 Q 32 234, 100 236 Q 168 234, 170 218 Q 178 120, 172 44 Z
Apply a horizontal glass gradient: left edge white 35% opacity β center near-transparent β right edge black 18% opacity.
**Collar:** rect x=24 y=36 width=152 height=12 rx=4, same glass treatment, 85% opacity.
**Lid:** rect x=26 y=22 width=148 height=18 rx=6. Metallic gradient top-to-bottom: white highlight β gold mid β darker gold bottom. Framer Motion spring rotation around left-edge hinge (x=26, y=31). Closes: 0Β°. Opens: -70Β°. Spring stiffness 260 open / 200 close, damping 14 open / 22 close.
**Glass highlights (clipped to jar shape via clipPath matching jar body path):**
- Left specular: tall narrow white ellipse, 30% opacity
- Right inner shadow: gradient rect right-to-left, 18% opacity
- Bottom glow: path matching jar curved bottom (not a rect), radial gradient whiteβtransparent
**Ground shadow:** blurred ellipse below jar, feGaussianBlur stdDeviation=6.
---
## Physics (matter-js, dynamic import)
Use Matter.Runner.run + requestAnimationFrame tick that calls setRenderTick(t+1) every frame to sync React renders to body positions.
**Static bodies:**
- Jar left/right inner walls (JAR_WALL_THICKNESS=24px, from collar to bottom)
- Curved jar floor: 6 angled rectangle segments tracing points (30,218)β(40,226)β(65,233)β(100,236)β(135,233)β(160,226)β(170,218) in SVG coords β scale to container coords, center each segment between adjacent pts, angle = atan2(dy,dx)
- Button floors: one thin rectangle per button, at the button's top edge
- Outer walls: left, right, floor, top ceiling
**Dynamic bodies:** circle r=17px per emoji, friction=0.55, frictionAir=0.06, restitution=0.05, density=0.0018.
**Seeding:** on mount, spawn one body per emotion inside the jar at random positions in the lower 60% of the interior.
**Dispense on click:**
1. Open lid (setLidOpen true), schedule close after 900ms
2. Find a matching interior body; if none, use a mouth-level spawn
3. Teleport the body to just above the jar mouth (exitY = jarTop - 18)
4. Apply velocity: upKick = -14 (upward), hVel = (targetButtonCenter - mouthX) / 60
5. Mark body as 'outside'; increment dispensedTotal
6. After 600ms delay, spawn a replacement at the jar interior top (below the collar) so it falls naturally onto the pile
**Cap:** max 30 outside bodies (remove oldest when exceeded). Max 8 inside bodies.
**Render:** inside bodies render in a z-[15] abs overlay (no animation). Outside bodies render in a z-20 abs overlay with Framer Motion fade-in (opacity 0β1, scale 0.6β1, duration 0.2s). Both use absolute positioning synced to body.position each frame.
---
## Banner
After 30 total dispenses, show a red banner (bg #ef4444, hard box-shadow 4px 4px 0px black 50%) centered on the jar with text "Full. Please Stop." Spring animate in (scale 0.75β1). Banner is z-50 inside a relative wrapper that wraps only the jar, so left-1/2 top-1/2 -translate centers on the jar, not the full container.
---
## Dual theme
Dark default. Light mode: lighter glass fill, darker stroke, adjusted lid colors. Both modes use useTheme() from ThemeProvider. All inline style colors switch on isDark.
---
## Requirements
- 'use client' at top
- export default function JarOfEmotions()
- Cleanup: cancel RAF, stop runner, clear world, disconnect ResizeObserver on unmount
- ResizeObserver rebuilds all static bodies on container resize
- Works at 320px width without overflow