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.
Mood Tracker is a "How are you feeling?" check-in card built around six hand-drawn SVG mood characters, each with its own body shape, color, and expression so they read instantly side by side. Drag the segmented slider, tap a face in the legend, or use the arrow keys, and the big focal face springs in while the feeling word crossfades and a soft liquid-gradient panel of drifting blobs retints to the chosen mood. A Save button morphs into a "Recorded" confirmation on click, and every surface tints to follow the mood in both light and dark themes with reduced-motion support. Drop it into a wellness or mood-journaling app, a daily reflection prompt, or a lightweight employee check-in.
Built with
MotionTailwind CSS
Frequently asked questions
Is Mood Tracker free to use?
Yes. Mood Tracker 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 Mood Tracker?
One command: npx shadcn@latest add https://aicanvas.me/r/mood-tracker.json, free with a free AI Canvas account. Or connect the AI Canvas MCP server and ask your AI editor to install Mood Tracker 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 Mood Tracker built with?
Mood Tracker is built with React and TypeScript, using Motion and Tailwind CSS. It ships with both light and dark styling.
Where would I use Mood Tracker?
Common uses include Wellness app, Journaling app, and Employee check-in. Like every AI Canvas component, it is self-contained and drops into any React project.
Can I remix Mood Tracker with AI?
Yes. Mood Tracker 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 Mood Tracker. 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/mood-tracker
AI prompt for Mood Tracker
Before writing any code, verify the project is set up with Tailwind CSS v4, TypeScript, and React. If any are missing, scaffold them (use the shadcn CLI to initialize Tailwind + the project if needed). Then build the component below as a single self-contained file.
Build a single-file, copy-paste-ready React + TypeScript component called `MoodTracker`:
a "How are you feeling?" mood check-in CARD (not a score). Drag a segmented slider
(or tap a face) through six expressive character moods; the big face, the feeling
word, and the whole card's colors all shift smoothly with the chosen mood. A Save
button records it.
STACK
- React + TypeScript (strict, no `any`).
- Tailwind CSS v4 for layout/utility classes.
- Framer Motion is the ONLY external dependency (`npm install framer-motion`).
- No icon library — all six faces AND the Save/check/bookmark icons are inline SVG.
- Default export `function MoodTracker()`. Root element is the only one with
`min-h-screen`: `className="flex min-h-screen w-full items-center justify-center p-4"`,
with an inline page background (dark `#0E0E10`, light `#EDEDE7`). The card sits
centered, `maxWidth: 380`, `rounded-3xl`, `p-5`, overflow-hidden.
- Font stack: `Manrope, ui-sans-serif, system-ui, -apple-system, sans-serif`.
DESIGN SCALE (strict)
- Type scale (px): 10 · 12 · 14 · 16 · 20 · 24 · 28. The feeling word sits at 24px
(font-extrabold). The big mood face is the single focal element and may exceed the
cap via clamp() (~96–140px). Every other text size snaps to a step.
- Spacing on a 2px grid favouring 4px steps (4 · 8 · 12 · 16 · 20 · 24; 2 · 6 · 10 · 14
allowed). Tailwind gap-2=8, gap-3=12, gap-4=16, p-4=16, etc.
- 44px minimum touch target on the slider track and every legend/Save button.
THE SIX MOODS — in this exact slider/legend order. Each is a DISTINCT inline SVG
character (own body shape + color + expression) drawn on a 0–64 viewBox; they share
one eye/mouth vocabulary so they read as a set. Default-selected mood is index 2 (Happy).
A shared facial stroke width of 2.6 is used for most features.
1. Frustrated — color `#CB5E3E` (red). Rounded-square body (rx 16) with a subtle
top-down white sheen overlay (~0.18 opacity). Angry down-slanted V brows, two
small filled eyes (r 3.4), a tense flat grimace mouth. Feature ink `#5A1E0E`.
2. Surprised — color `#E5A85E` (tan). Scalloped flower/cloud body built from a chain
of 9 quadratic-arc lobes (inner radius 21, outer radius 26, centered at 32,32).
Wide round open eyes (white r 5 + ink pupil r 2.4), a small open "o" mouth
(ellipse rx 3.4 ry 4.4). Feature ink `#6B3F12`.
3. Happy — color `#F2D44E` (yellow). Circle body (r 25). Two upward-arc smiling eyes,
a big filled open grin, plus rosy cheeks (`#F0926B` r 4, ~0.7 opacity). Feature ink
`#6E5806`.
4. Uneasy — color `#A9C95E` (green). Lumpy wavy organic blob (cubic-curve loop).
Slightly worried eyes (small filled circles + tiny upper-lid arcs), a squiggly wavy
mouth (`Q…T…T` path). Feature ink `#3F551A`.
5. Sad — color `#8AC7D8` (light blue). Dome / rounded-top half-shape (rounded top,
flatter bottom). Downturned eyes (arcs curving down), a single light tear
(`#E6F4F8`), a frown. Feature ink `#1E5663`.
6. Anxious — color `#BA8FD4` (lavender). Rounded-diamond body (a 45°-rotated square with
soft corners). Worried slanted brows, two small uneasy eyes (r 3.2), a small frown.
Feature ink `#4A2A66`.
COLOR / LERP HELPERS (inline, no libs)
- hexToRgb / rgbToHex / `lerpHex(a, b, t)` linear-interpolate two hex colors.
- `tintHex(hex, t)` = lerp toward white `#FFFFFF` (lighten).
- `shadeHex(hex, t)` = lerp toward a soft dark neutral `DARK_NEUTRAL = '#1B1B22'`
(NEVER near-black, so dark theme stays colored, not muddy).
- `inkHex(hex)` = lerp toward near-black `#14140F` by 0.82 — a deep, mood-tinted ink
that stays AA-legible on a soft same-hue wash while keeping a trace of hue.
- `clamp(n, lo, hi)`.
MOOD-DERIVED WASHES (recompute via useMemo on mood color + theme)
The focal panel is a soft, airy, same-hue wash of the CURRENT mood (light → toward
white; dark → toward the soft dark neutral). Exact lerp amounts:
- baseTint = isDark ? shadeHex(color, 0.5) : tintHex(color, 0.5) // "base" blobs
- softTint = isDark ? shadeHex(color, 0.44) : tintHex(color, 0.66) // "soft" blobs
- panelWash = isDark ? shadeHex(color, 0.56) : tintHex(color, 0.62) // flat panel fill
- panelInk = isDark ? tintHex(color, 0.88) : inkHex(color) // feeling-word ink (AA)
- cardTint = isDark ? lerpHex('#1A1A1E', color, 0.1) : lerpHex('#FFFFFF', color, 0.06) // card surface
- deepAccent = lerpHex(color, '#000000', 0.18) // accent rail / handle ring / chips
- Neutral chrome (theme, not mood): titleColor dark `#F2F2F0` / light `#16160F`;
subColor dark `#8A8A86` / light `#6B6B62`; legendIdle dark `#26262C` / light `#F1F1EC`.
LAYOUT (top to bottom inside the card; content layer above an ambient glow)
- AMBIENT GLOW: a pointer-events-none absolute-inset radial-gradient of the mood color
blooming behind the panel — `radial-gradient(120% 70% at 50% 28%, {color}{alpha} 0%, {color}00 60%)`
with alpha hex `24` (dark) / `1F` (light). Transitions background 0.5s ease.
- HEADER ROW (flex, space-between): left = "How are you feeling?" (16px, font-bold) over a
subtitle "Today · 6-day streak" (12px, subColor, marginTop 6). Right = the SAVE BUTTON.
- FOCAL PANEL: rounded-2xl, minHeight 200, vertical padding 20, background = panelWash,
inset hairline box-shadow (`inset 0 0 0 1px {tint}…`), overflow-hidden, transition
background 0.5s ease. It contains:
• LIQUID MESH: an absolute-inset layer with `filter: blur(26px)` holding 5 drifting
radial blobs. Each blob: rounded-full, aspect 1/1, positioned by % with -50%/-50%
translate, background `radial-gradient(circle at 50% 50%, {color}, {color}00 70%)`,
opacity 0.85 (dark) / 0.7 (light), transition background 0.45s ease. The blob set
(x%, y%, size% of panel width, tint slot, drift dx%, dy%, duration s):
{24,30,72,base,8,6,11} {76,26,66,soft,7,8,13} {34,76,78,soft,9,5,15}
{80,74,60,base,6,9,12} {52,50,54,base,10,7,17}
Tint slot 'base' uses baseTint, 'soft' uses softTint. Each blob drifts forever via
Framer Motion: animate x:[0, dx, -dx*0.6, 0], y:[0, -dy, dy*0.7, 0], repeat Infinity,
easeInOut, its own duration.
• READABILITY LIFT: an absolute-inset gradient keeping the feeling word legible —
dark: `radial-gradient(120% 90% at 50% 40%, rgba(0,0,0,0) 0%, rgba(0,0,0,0.18) 100%)`;
light: `radial-gradient(120% 90% at 50% 42%, rgba(255,255,255,0.32) 0%, rgba(255,255,255,0) 62%)`.
• BIG FACE: in a clamp(96px, 30vw, 140px) square, render the current mood's SVG at
size 140 with a drop-shadow. On mood change it crossfades + spring-pops via
AnimatePresence mode="popLayout": initial {opacity:0, scale:0.78, y:8},
animate {opacity:1, scale:1, y:0}, exit {opacity:0, scale:0.78, y:-8},
spring {stiffness:360, damping:22}.
• FEELING WORD: the mood label at 24px font-extrabold, color = panelInk,
letterSpacing -0.01em, a soft theme-aware textShadow. Crossfades on change
(AnimatePresence popLayout): initial {opacity:0, y:8, blur(4px)} →
{opacity:1, y:0, blur(0)} → exit {opacity:0, y:-8, blur(4px)}, 0.26s easeOut.
NO number anywhere.
- SEGMENTED SLIDER: label "Drag to set your mood" (12px, subColor) above a track that
is the slider control itself — role="slider", tabIndex 0, height 44 (the hit area),
touch-none, select-none, cursor-pointer. ARIA: aria-valuemin 1, aria-valuemax 6,
aria-valuenow = index+1, aria-valuetext = current mood label, aria-describedby the
feeling-word id, aria-label "Mood".
• The visible rail is a centered 12px-tall rounded-full bar (top 16) split into 6
equal flex segments, each filled with its mood color in order.
• HANDLE: a 36×36 white circle, 4px border in deepAccent, drop-shadow, with a 12×12
inner dot in the current mood color (transition 0.35s). Its left = ((index+0.5)/6)*100%,
animated via spring {stiffness:480, damping:34}; marginLeft -18 to center it.
• DRAG: pointer-capture on pointerdown; map clientX over the track rect to the nearest
of 6 stops (stops at the CENTER of each segment): ratio = clamp((x-left)/width, 0, 1);
index = clamp(round(ratio*6 - 0.5), 0, 5). pointermove while dragging updates it;
pointerup/cancel release capture.
• KEYBOARD: ArrowLeft/ArrowDown → prev, ArrowRight/ArrowUp → next, Home → 0, End → 5
(preventDefault on handled keys).
- LEGEND ROW: a 6-column grid (gap-1.5 = 6px, marginTop 4) of tappable face buttons (no
counts). Each button: real <button> with aria-label = mood, aria-pressed = active,
minHeight 44, padding 8, rounded-xl; background `{color}22` when active else transparent.
Inside, a 36×36 rounded-full chip holding the mood SVG at size 28; when active the chip
gets background = legendIdle, a `0 0 0 2px {color}` ring, and spring-scales to 1.08
({stiffness:420, damping:24}). Clicking sets that mood.
SAVE BUTTON STATE MACHINE
- A real <button> with a ≥44×44 hit area wrapping a compact pill (font 12, semibold,
rounded-full, vertical padding 6, horizontal padding 10, gap-1).
- IDLE: pill shows an inline bookmark SVG + "Save"; background = a faint tint of the
current mood (`{color}26` dark / `{color}1F` light), inset 1px ring of the mood color,
text = `#F2F2F0` (dark) / inkHex(color) (light). whileHover scale 1.04 + brightness-110
(pointer devices only), whileTap scale 0.94, spring {stiffness:480, damping:28}.
- ON CLICK: morph to CONFIRMING for ~1.6s — background/ring/text become a calm success
green `CONFIRM_GREEN = '#3FA66A'` (bg `{green}33` dark / `{green}24` light, ring `{green}55`),
the icon swaps to an inline check, the label becomes "Recorded", and the pill does a
one-shot scale pop [1, 1.12, 1] over 0.36s. Color/bg/ring transition 0.3s ease.
Then it reverts to idle. Re-clicks are BLOCKED while confirming (disabled). aria-label
flips to "Mood recorded"; the button is aria-live="polite". Clean up the setTimeout on
unmount.
DUAL THEME (portable, no provider)
Inline a card-wrapper-aware `useTheme` hook: it resolves the active theme from the
nearest `[data-card-theme]` ancestor when present (reading whether it has the `dark`
class), otherwise falls back to the `dark` class on `<html>`. Keep it in sync with a
MutationObserver watching the class attribute of BOTH `<html>` AND the card wrapper
(when present); disconnect on cleanup. Use a useLayoutEffect that is isomorphic-safe.
This is what lets every inline hex branch per theme.
MOTION / A11Y
- Respect `useReducedMotion()`: when reduced, freeze blob drift, make crossfades/pop/glow
instant (duration 0), and skip hover/tap scale and the entrance.
- Gentle entrance: the card rises + fades on mount (initial {opacity:0, y:16} →
{opacity:1, y:0}, spring {stiffness:220, damping:26}); skipped under reduced motion.
- Fully responsive down to 320px (no fixed layout widths; clamp() on the big face).
- 44px touch targets; full keyboard slider; decorative SVG parts aria-hidden /
focusable="false".
KEY CONSTANTS RECAP
- Six mood hex: Frustrated #CB5E3E · Surprised #E5A85E · Happy #F2D44E · Uneasy #A9C95E
· Sad #8AC7D8 · Anxious #BA8FD4. Default index 2 (Happy).
- DARK_NEUTRAL #1B1B22 · inkHex target #14140F @0.82 · CONFIRM_GREEN #3FA66A.
- Panel/wash lerps: base 0.5 · soft 0.44(dark)/0.66(light) · panelWash 0.56(dark)/0.62(light)
· panelInk tint 0.88(dark)/inkHex(light) · cardTint 0.1(dark)/0.06(light) · deepAccent 0.18.
- "Recorded" hold 1600ms; save pop 0.36s [1,1.12,1].
- Springs: card-enter 220/26 · face-pop 360/22 · handle 480/34 · legend 420/24 · save 480/28.
- Blob mesh blur 26px; 5 blobs as listed; face SVG stroke 2.6.