Animation Only Feedback
Status: ready
Symptom
The user knows the action worked only because something moved. The product image flies into the cart, a checkmark bounces, a pin pulses. Nothing on screen changes in a way that stays.
User taps Add to cart.
The image flies to the cart icon.
The cart count doesn't change.
VoiceOver says nothing.
Where it shows up
Add to cart, favorites, payment success, upload completion, refresh, onboarding steps, drag and drop.
Why it blocks the task
A VoiceOver user doesn't see the animation. A user with Reduce Motion on may get no animation at all. A user who looked away for 400 ms missed it. None of them knows whether the item is in the cart, so they tap again and end up with two.
Animation can make success feel good. It can't be the only proof of success.
Root cause
The animation is the message.
- No state stays on screen after the animation ends.
- The count or value doesn't update.
- VoiceOver gets no result.
- The Reduce Motion branch removes the animation and with it the only feedback.
Before
Button("Add to cart") {
add(tea)
flyToCart = true
}
Fix
Verdict: every result has a state that stays: text, a count, a changed button. The animation is on top, and Reduce Motion only changes the animation.
Why: the state is there for everyone, whether they saw the motion or not. VoiceOver reads it when the user gets there, and gets one short announcement for the result.
@Environment(\.accessibilityReduceMotion) private var reduceMotion
Button("Add to cart") {
add(tea)
withAnimation(reduceMotion ? .easeInOut(duration: 0.2) : .spring) {
added = true
}
AccessibilityNotification.Announcement("Added to cart. \(cart.count) items.").post()
}
if added {
Label("Added to cart", systemImage: "checkmark.circle") // shown with or without Reduce Motion
}
CartButton()
.accessibilityValue("\(cart.count) items")
With Reduce Motion, the confirmation fades in instead of flying. The text is the same in both cases. The common mistake is the opposite: if reduceMotion { Text("Added") } else { animation }. That hides the confirmation from everyone who didn't turn the setting on, including VoiceOver users.
Progress: the value, not the bar animation.
ProgressView("Uploading receipt", value: progress)
What we don't do
- We don't show the confirmation only when Reduce Motion is on.
- We don't use an announcement as the only feedback. It's spoken once and gone. The state stays.
Check
Profile: Reduce Motion off, and then on
Task: add jasmine tea to the cart
Expected: "Added to cart" is visible in both cases
the cart count changes in both cases
with Reduce Motion, no flying image
Profile: VoiceOver
Task: add an item, then move to the cart button
Expected: one announcement "Added to cart. 3 items."
the cart button reads "3 items" as its value
WCAG mapping
2.3.3 Animation from Interactions: interaction-triggered motion can be turned off.4.1.3 Status Messages: the result is available without seeing it.1.3.1 Info and Relationships: the result is not only visual.