Motion polish: where the SwiftUI animation catalogue fits #4

Open
opened 2026-09-03 17:46:39 +00:00 by kayg · 0 comments
Owner

Where the animation catalogue in research/swiftui-animations-2026.md earns its place. Ranked by payoff ÷ risk. Every item is gated by Reduce Motion (and Reduce Transparency where glass is involved); use existing MailMotion tokens.

Do now (ties into work in flight)

  1. Reader tab strip — GlassEffectContainer + glassEffectID (macOS 26). Active pill .glassEffect(.regular.interactive(), in: .capsule); promote-transient and close-tab liquid-morph via .glassEffectTransition(.matchedGeometry) instead of popping. Hover card stays non-glass (glass must not sample glass). Fallback: quaternary fill under Reduce Transparency. (briefed to the tab-bar slice already)
  2. Sidebar unread badges + message-list title count — .contentTransition(.numericText(value:)). Counts roll like an odometer as sync lands mail. Cheapest visible "alive" signal.
  3. Folder sync-state indicator — symbol effects instead of one generic spinner.
    • downloading history: arrow.down.circle.dotted + .symbolEffect(.variableColor.iterative)
    • indexing: arrow.triangle.2.circlepath + .symbolEffect(.rotate)
    • idle/connected: .breathe (calmer than .pulse)
      Needs a FolderActivity kind surfaced from the sync engine (backfill walking vs. FTS/index work) so the sidebar can pick the glyph.
  4. Flag / read toggles — .symbolEffect(.replace.magic(fallback:)) on flag ↔ flag.fill, envelope ↔ envelope.open; one-shot .bounce on Archive/Trash in the reader actions cluster when the action commits.

Next

  1. Reader open — matchedGeometryEffect hero from the list row subject to the reader header when a tab opens; .blurReplace for the body swap.
  2. Message list — .scrollTransition + .visualEffect row fade/scale at the top edge under the title (matches the pane-edge dissolve); .scrollEdgeEffectStyle(.soft) for the reader content under the tab strip.
  3. Search panel — .phaseAnimator lift→settle for result-row insertion; .snappy springs for present/dismiss instead of plain opacity.
  4. Toasts — keyframeAnimator anticipation-then-slide for "Message was deleted" / "Couldn't move N messages".

Skip

  • TextRenderer typewriter effects — gimmick for a mail client.
  • .drawOn / .drawOff — only for onboarding/empty states (maybe the "Add an account" illustration).
  • .wiggle — only for rejected input (bad password in the account editor is fine).

Notes

  • .hoverEffect and .navigationTransition(.zoom) are not available on macOS; use .onHover + pointerStyle and matchedGeometryEffect respectively.
  • Native NSToolbar items already get Liquid Glass from AppKit; .glassEffect only applies to SwiftUI-drawn chrome (tab strip, search card, composer card).
  • Sources and availability lines: research/swiftui-animations-2026.md.
Where the animation catalogue in `research/swiftui-animations-2026.md` earns its place. Ranked by payoff ÷ risk. Every item is gated by Reduce Motion (and Reduce Transparency where glass is involved); use existing `MailMotion` tokens. ## Do now (ties into work in flight) 1. **Reader tab strip — `GlassEffectContainer` + `glassEffectID`** (macOS 26). Active pill `.glassEffect(.regular.interactive(), in: .capsule)`; promote-transient and close-tab liquid-morph via `.glassEffectTransition(.matchedGeometry)` instead of popping. Hover card stays non-glass (glass must not sample glass). Fallback: quaternary fill under Reduce Transparency. *(briefed to the tab-bar slice already)* 2. **Sidebar unread badges + message-list title count — `.contentTransition(.numericText(value:))`.** Counts roll like an odometer as sync lands mail. Cheapest visible "alive" signal. 3. **Folder sync-state indicator — symbol effects instead of one generic spinner.** - downloading history: `arrow.down.circle.dotted` + `.symbolEffect(.variableColor.iterative)` - indexing: `arrow.triangle.2.circlepath` + `.symbolEffect(.rotate)` - idle/connected: `.breathe` (calmer than `.pulse`) Needs a `FolderActivity` kind surfaced from the sync engine (backfill walking vs. FTS/index work) so the sidebar can pick the glyph. 4. **Flag / read toggles — `.symbolEffect(.replace.magic(fallback:))`** on `flag ↔ flag.fill`, `envelope ↔ envelope.open`; one-shot `.bounce` on Archive/Trash in the reader actions cluster when the action commits. ## Next 5. **Reader open — `matchedGeometryEffect`** hero from the list row subject to the reader header when a tab opens; `.blurReplace` for the body swap. 6. **Message list — `.scrollTransition` + `.visualEffect`** row fade/scale at the top edge under the title (matches the pane-edge dissolve); `.scrollEdgeEffectStyle(.soft)` for the reader content under the tab strip. 7. **Search panel — `.phaseAnimator`** lift→settle for result-row insertion; `.snappy` springs for present/dismiss instead of plain opacity. 8. **Toasts — `keyframeAnimator`** anticipation-then-slide for "Message was deleted" / "Couldn't move N messages". ## Skip - `TextRenderer` typewriter effects — gimmick for a mail client. - `.drawOn` / `.drawOff` — only for onboarding/empty states (maybe the "Add an account" illustration). - `.wiggle` — only for rejected input (bad password in the account editor is fine). ## Notes - `.hoverEffect` and `.navigationTransition(.zoom)` are not available on macOS; use `.onHover` + `pointerStyle` and `matchedGeometryEffect` respectively. - Native `NSToolbar` items already get Liquid Glass from AppKit; `.glassEffect` only applies to SwiftUI-drawn chrome (tab strip, search card, composer card). - Sources and availability lines: `research/swiftui-animations-2026.md`.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/mailternal#4
No description provided.