The motion rules I wrote for Sayforth
Sayforth is a thirty day speaking program for iPhone. You answer one question out loud each day, it measures the answer, and you carry one change into tomorrow. Before anything was animated, I wrote down the rules. Claude writes the code for the app, and the rules are what I hold it to. That ordering was the single best decision in the project.
The frequency gate
Every animation has to pass one test before it is allowed to exist: how often will someone see this?
- Hundreds of times a day. Tab switches, scrolling, keyboard, settings toggles. Platform default or nothing.
- Tens of times a day. Press feedback, row selection. Under 150ms, near-imperceptible, or nothing.
- Occasionally. Sheets, modals, toasts. Standard treatment.
- Rarely. First run, session complete, personal best. The only place a delight budget exists.
The gate has a companion rule that does most of the enforcement: every animation has to be given a named purpose, one of feedback, spatial consistency, state indication, preventing a jarring change, or rare delight. Motion that cannot be named gets deleted. I have deleted more animations than I have shipped, which felt like failure for about a month and then started to feel like the method.
Never ease-in
One curve is banned outright, and the reason fits in a sentence: ease-in starts slow, which delays the exact moment the user is watching. Entrances and exits get ease-out. Things moving around on screen get ease-in-out. Progress runs linear. In SwiftUI that comes out as two timing curves and a rule about springs:
// Entrances and exits
.timingCurve(0.23, 1, 0.32, 1, duration: 0.2)
// Moving on screen
.timingCurve(0.77, 0, 0.175, 1, duration: 0.25)
// Never .easeIn
Springs start critically damped, at a damping fraction of 1.0. Bounce gets added only when the gesture itself carried momentum. Or, shorter: if a finger was involved, use a spring. Nothing ever scales from zero, because nothing in the real world appears out of nothing; entrances start at 0.95 and fade up. A press scales to 0.97 over 0.12 seconds, and that is the most frequently seen animation in the app by an enormous margin.
Four things are allowed to move
The whole app gets four signature motions, each tied to one meaning. The level meter while you are recording. The feedback card arriving after a session. One repair moment in the day row. The end-of-programme report, appearing once per season.
Only two of them carry a haptic: the repair moment gets a single light impact on the settling frame, and the end-of-programme report gets one success notification. The feedback card carries none, because a haptic there made the app feel like it was congratulating you on a number, and there are no scores in Sayforth. The four haptic types in the whole app are selection ticks, a light impact for snapping home, a medium impact for anything destructive, and success or error for outcomes. Three rules govern all of them: same frame as the visual, one per user action and never on scroll, and never the only feedback.
The rule I keep coming back to is about interruption. Always animate from the current presentation value, never the target, and never lock out input while a transition plays. It is the least visible rule in the document and the one that separates an app that feels responsive from one that feels like it is thinking.
Reduced motion, by the way, is fewer and gentler, not zero. Turning accessibility settings on should not hand someone a worse product.