Motion audit · design-motion-principles

Tally — what to slow down, speed up, and finish

A pass over Tally's core loop — check-off, tab switches, the add-habit sheet, and the streak milestones — read through three motion practitioners' lenses, ordered by what users feel most and the order to fix it.

What it isHabit & streak tracker, used in quick daily bursts
StackMobile web · React + Framer Motion
Overall

Tally has real motion personality — but it's uneven. The highest-frequency action, the check-off, is the most over-animated; the add-habit sheet that should glide just snaps shut. Tighten the frequent moments toward speed, finish the half-built transitions, then spend delight where it's earned: the streaks.

01

How each practitioner reads the motion

Three lenses, weighted for this context — a frequently-opened utility where most motion should get out of the way, and a little should be memorable. The read is what each would push on hardest today.

LensVerdictOne-line read
Restraint & Speed Emil KowalskiSecondary Concern The check-off and tab switches are over-animated for actions this frequent — durations run long.
Production Polish Jakub KrehelPrimary Problem Several transitions are half-built: enters without exits, state changes that snap. Craft is uneven.
Experimentation & Delight Jhey TompkinsSelective Strong Restraint is mostly right. The open prize is making the streak milestones actually feel earned.
02

Where the timings land

Duration is a budget. A 320ms sheet is correct — a large surface earns the time. A 600ms tab switch on an action you fire dozens of times a session is the mistake. Each dot is one of Tally's animations, plotted where it runs today.

0 100 300 500 600ms INSTANT RESPONSIVE DELIBERATE SLUGGISH 5 3 7 6 4 2 1 fire most often

The animations

  1. 1Tab switch600ms
  2. 2Check-off (bounce)450ms
  3. 3Card hover lift80ms
  4. 4Add-habit sheet enter320ms
  5. 5Streak counter changenone
  6. 6Page route250ms
  7. 7Toast200ms
Instant / Responsive · 0–300ms — where almost everything belongs. Taps, toggles, tabs, hovers.
Deliberate · 300–500ms — earned by large surfaces: sheets, modals, full-screen routes.
Sluggish · 500ms+ — feels laggy. Reserve for rare, deliberately cinematic moments — or nothing.
What's off

The two dots furthest right — tab switch (1) and check-off (2) — are the actions that fire most. Frequency and duration are inversely related: the more often it runs, the faster it should be. And the streak counter (5) has no transition at all.

03

Jakub Krehel — Production Polish

Primary lens

Problem

What's working well
  • Page routes use a clean opacity + 8px translate at 250ms — the right shape and duration. routes/transition.tsx:14
  • Toasts enter and exit symmetrically — the exit isn't an afterthought. ui/Toast.tsx:31
Issues to address
CriticalJakub

The add-habit sheet enters, then snaps shut with no exit

WhatThe bottom sheet animates up on open (320ms, good), but on dismiss it's removed from the tree instantly — no exit. The component renders conditionally with no AnimatePresence wrapper, so Framer Motion never gets to play the exit.

Why it mattersA surface that glides in and vanishes reads as broken — the eye expects symmetry. It's the single most common "half-built motion" tell, and it's on the app's primary create flow.

Recommended motionWrap the sheet in AnimatePresence and mirror the enter: slide down + fade over 300ms with the same ease-out-quint. The demo shows the enter; the exit is its reverse.

screens/Habits/AddSheet.tsx:48

Sheet enter (mirror for exit)300ms · ease-out-quint
New habit
Drink water Add
ImportantJakub

Streak counter jumps between values with no transition

WhatWhen a streak increments, the number is replaced in place — a hard swap. There's no transition on the value change, so the most rewarding number in the app updates with the least ceremony.

Why it mattersThe streak count is the payoff of the whole interaction. A snap makes a hard-won number feel like a re-render, not an achievement.

Recommended motionRoll the new value in: opacity + 10px translateY + a 5px blur that clears, 220ms. Subtle, but it tells the eye something changed and it's good.

components/StreakBadge.tsx:22

Number roll-in220ms · opacity + Y + blur
Current streak
7 days
Opportunities
  • 💡Habit cards lean on a drop shadow that's invisible on the dark theme — a 1px border would carry the elevation on both. components/HabitCard.tsx:9
Through Jakub's lens

The vocabulary is right; the sentences are unfinished. Pair every enter with an exit, give the streak its moment, and Tally crosses from "animated" to "polished."

04

Emil Kowalski — Restraint & Speed

Secondary lens

Concern

What's working well
  • Card hover lift is 80ms — instant, exactly right for a passive affordance. components/HabitCard.tsx:18
  • No animation on keyboard-driven navigation — keyboard users aren't taxed with motion they didn't ask for.
Issues to address
CriticalEmil

Tab switches slide for 600ms — far too slow for the most frequent action

WhatThe four bottom tabs cross-slide the full panel width over 600ms with an ease-in-out. Tabs are the app's highest-frequency navigation; a 600ms slide means every switch holds the user behind an animation.

Why it mattersEmil's rule: the more often an action fires, the less it should animate. At this duration the motion stops being feedback and becomes a toll. ease-in-out also adds a slow start, compounding the lag.

Recommended motionDrop to a 180ms opacity crossfade with a 7px slide, ease-out. Better still: consider no slide at all — a fast crossfade is plenty of orientation for a tab.

navigation/TabView.tsx:63

Quick tab crossfade180ms · ease-out
Today
Morning walk
Read 10 pages
ImportantEmil

Check-off pops from scale(0) with a spring bounce

WhatTicking a habit animates the checkmark from scale(0) with a bouncy spring (~450ms to settle). It's the app's core, most-repeated gesture, and it's the showiest animation in the product.

Why it mattersBounce on a high-frequency confirm gets tiring fast — the overshoot draws attention to motion the user has already mentally completed. Starting from scale(0) exaggerates the distance and the time.

Recommended motionScale 0.9 → 1 with opacity, 200ms ease-out, no overshoot. Confident and done before the finger lifts.

components/HabitCheck.tsx:27

Calm check, no bounce200ms · ease-out
Drink water
Through Emil's lens

Speed up everything the user touches constantly. The tab switch and the check-off should feel instant; their current durations are the difference between an app that feels fast and one that feels fussy.

05

Jhey Tompkins — Experimentation & Delight

Selective lens

Strong

What's working well
  • Tally resists the urge to animate everything — restraint is the right default for a daily utility. Delight is rationed, which makes room for it to land where it counts.
Issues to address
ImportantJhey

Hitting a milestone streak passes by with no celebration

WhatCrossing a 7-, 30-, or 100-day streak looks identical to any other day — the number just increments. The one moment in the app that has genuinely earned a flourish gets none.

Why it mattersThis is where delight pays for itself. A milestone is rare, emotionally loaded, and shareable — exactly the place to spend motion the rest of the app withholds. Skipping it leaves the payoff flat.

Recommended motionOn a milestone only: a badge that scales 0.8 → 1 (260ms ease-out) with a short, one-shot sparkle burst. Fires once on the event — not a looping pulse.

components/StreakBadge.tsx:40

Milestone badge enter260ms · ease-out + sparkle
7 DAY
Opportunities
  • 💡The today-list could stagger its rows in on first paint — 30ms apart, opacity + 6px rise. One orchestrated load beats scattered micro-interactions. screens/Today.tsx:51
Through Jhey's lens

Don't add more motion — add it in one right place. The streak milestone is the moment worth engineering; everywhere else, keeping your hands off the controls is the sophisticated move.

06

In the order I'd fix them

Ordered by what users feel: critical (degrades the core loop on every use) → important (real friction or a missed payoff) → opportunity (could enhance).

Critical · must fix2
IssueFileFix
Tab switch runs 600ms on the highest-frequency actionTabView.tsx:63180ms opacity crossfade + 7px slide, ease-out
Add-habit sheet has no exit — snaps shutAddSheet.tsx:48Wrap in AnimatePresence; mirror the 300ms enter on exit
Important · should fix3
IssueFileFix
Check-off pops from scale(0) with a bounceHabitCheck.tsx:27scale 0.9→1 + opacity, 200ms ease-out, no overshoot
Streak counter swaps with no transitionStreakBadge.tsx:22220ms opacity + translateY + blur roll-in
Milestone streaks have no celebration momentStreakBadge.tsx:40One-shot badge scale-in + sparkle, milestones only
Opportunities · could enhance2
EnhancementWhereImpact
Habit-card elevation invisible on dark themeHabitCard.tsx:91px border carries elevation on both themes
Today-list could stagger in on first paintToday.tsx:5130ms stagger, opacity + 6px rise — one orchestrated load
07

Which lens carried this audit

Referenced most

Jakub Krehel — Production Polish

Tally's gaps are craft gaps, not taste gaps: enters without exits, state changes that snap. That's Jakub's territory — finishing what's been started — so his lens drove the ordering. Emil set the durations; Jhey marked the one place to spend.

  • Lean EmilTreat every duration as a budget. Push tab, check, and toggle timings under 200ms and question any motion on a high-frequency action.
  • Lean JakubAudit every conditional render for a missing exit. Pair enters and exits, and give meaningful state changes a transition.
  • Lean JheyPick the single highest-emotion moment — the milestone — and over-invest there, with @property, springs, or scroll-driven touches.