Foundations
Motion
Decisive spring-like entries, fast exits, nothing bouncy. Motion communicates layer changes — a sheet rising, a calendar unfolding — never delight for its own sake.
Sheet rise — the signature curve
Every sheet enters with cubic-bezier(0.32, 0.72, 0, 1) over 0.48s: fast initial commitment, long gentle settle. Exits use the inverse curve with duration scaled to remaining travel — a half-dismissed sheet finishes quickly instead of replaying the full animation.
Micro-motion, live
Step dots: width 7→20px · 0.25s
Syncing…
Sync pulse: opacity 1→0.4 · 0.8s loop
syncSpin: 0.9s linear infinite
Specification
| Motion | Spec | Usage |
|---|---|---|
| Sheet enter | sheetRise · 0.48s · cubic-bezier(0.32, 0.72, 0, 1) | All bottom sheets and drawers |
| Sheet exit | cubic-bezier(1, 0, 0.68, 0.28) · duration scaled to remaining distance | Dismiss |
| Drag-to-dismiss | useSheetDrag() · 12px commit threshold | All sheets |
| Calendar | calExpand/calCollapse · 0.28s · cubic-bezier(0.34, 1.4, 0.64, 1) · origin top-right | Month calendar panel |
| Sync | syncSpin 0.9s linear · pulse opacity 1→0.4 | Sync indicator |
| Toast | fade/slide · auto-dismiss 2.4s | Save confirmations |
| Step dots | width 7→20px · 0.25s | Onboarding progress |
| Assumption handoff | sheet closes → target log screen opens after 320ms | Explainability → action |
The 320ms handoff
When a user taps an assumption action inside a drawer, the sheet closes and the matching log screen opens after 320ms. The pause is deliberate: it lets the dismissal finish reading as “that surface answered me” before the next surface asks for input.
Accessibility
- —All animations are layer transitions or status indicators — none carry sole meaning, so
prefers-reduced-motioncan safely shorten them to fades. - —Drag-to-dismiss (12px commit threshold) is always duplicated by an explicit close button and scrim tap.
- —Nothing autoplays on a loop except status indicators (sync), which pair with a text label.