Open any app you love and count the moments that make it feel alive: the heart that bursts when you double tap, the toggle that snaps into place, the checkmark that draws itself after a payment. None of these are features. They are micro interactions, and they are the difference between an interface that works and an interface that feels responsive.
This article is a teardown. We picked 10 real micro interaction examples from apps sitting on most iPhone home screens, measured what is actually happening (duration, easing, haptic type), and rebuilt each one in SwiftUI. At the end you will also find the part most articles skip: when motion stops helping and starts hurting usability.
What counts as a micro interaction?
A micro interaction is a single, contained moment of interaction built around one task. Dan Saffer’s classic model breaks it into four parts, and it is still the fastest way to audit your own app:
- Trigger: what starts it (a tap, a swipe, a system event, a data change).
- Rules: what is allowed to happen and in what order.
- Feedback: what the user sees, hears or feels (animation, sound, haptic).
- Loops and modes: what happens on repeat, on failure, or over time.
Most designers only design the feedback layer. The ones who design all four layers ship interfaces that feel considered instead of decorated.
The timing budget you should memorise
Timing is the single biggest reason a micro interaction feels premium or cheap. Use this as a default budget before you start tweaking:
| Interaction type | Duration | Curve |
|---|---|---|
| Tap state change, hover, highlight | 80 to 150 ms | ease-out |
| Toggle, checkbox, small transform | 180 to 250 ms | spring, damping 0.8 |
| Card, sheet, list row entry | 300 to 400 ms | spring, response 0.4 |
| Celebration or success moment | 500 to 800 ms | spring, damping 0.5 |
| Looping progress or waiting state | 1000 to 1400 ms per cycle | linear |
Rule of thumb: anything the user initiated should begin responding in under 100 ms. Anything the system initiated can afford to be slower, because the user is not waiting with their finger on the glass.

The 10 micro interaction examples at a glance
| # | Where you have seen it | Pattern | Approx. timing | Haptic |
|---|---|---|---|---|
| 1 | Double tap heart burst | 250 ms in, 250 ms out | Medium impact | |
| 2 | Apple Music, Spotify | Play / pause symbol morph | 150 ms | Selection |
| 3 | iOS Settings | Toggle knob snap | 220 ms spring | Light impact |
| 4 | Mail, Gmail | Swipe to archive threshold | Live + 300 ms snap | Rigid impact at threshold |
| 5 | X, Reddit, Mail | Pull to refresh | Live + 1200 ms loop | Light impact on release |
| 6 | App Store, Clock | Tab bar icon bounce | 350 ms | Selection |
| 7 | Apple Pay, banking apps | Self drawing success check | 600 ms | Notification success |
| 8 | Login screens everywhere | Error field shake | 400 ms, 3 oscillations | Notification error |
| 9 | Duolingo, Strava, Rings | Progress fill with overshoot | 700 ms | Soft impact on milestone |
| 10 | Photos, Messages | Long press lift and blur | 200 ms press, 300 ms lift | Medium impact on lift |
1. Instagram: the double tap heart burst
What actually happens
The heart does not simply fade in. It scales from roughly 30 percent to about 115 percent, settles back to 100 percent, holds for a fraction of a second, then fades out while scaling up slightly. The small overshoot is what reads as “joy” rather than “notification”.
- Trigger: double tap anywhere on the media, not just the heart button.
- Feedback: overlay heart + the toolbar heart filling in red at the same time, so the user learns where the permanent state lives.
- Haptic: a single medium impact fired at the start of the burst, never at the end.
SwiftUI recreation
struct DoubleTapHeart: View {
@State private var liked = false
@State private var burst = false
var body: some View {
Image("photo")
.resizable()
.scaledToFill()
.overlay {
Image(systemName: "heart.fill")
.font(.system(size: 92))
.foregroundStyle(.white)
.shadow(radius: 8)
.scaleEffect(burst ? 1 : 0.3)
.opacity(burst ? 1 : 0)
}
.onTapGesture(count: 2) {
liked.toggle()
withAnimation(.spring(response: 0.25, dampingFraction: 0.45)) {
burst = true
}
withAnimation(.easeOut(duration: 0.25).delay(0.45)) {
burst = false
}
}
.sensoryFeedback(.impact(weight: .medium), trigger: liked)
}
}
Where teams get it wrong: firing the haptic on every double tap including the un-like. Removing something should not feel as rewarding as adding it.
2. Apple Music: the play and pause symbol morph
What actually happens
The triangle does not cut to two bars. It cross-dissolves with a tiny directional offset, in about 150 ms. It is deliberately faster than any other animation on the screen because audio playback must feel instantaneous. Mobile App Micro Interactions That Boost UX covers this in more depth.
SwiftUI recreation
struct PlayButton: View {
@State private var isPlaying = false
var body: some View {
Button {
isPlaying.toggle()
} label: {
Image(systemName: isPlaying ? "pause.fill" : "play.fill")
.font(.system(size: 34))
.contentTransition(.symbolEffect(.replace.offUp))
}
.buttonStyle(.plain)
.sensoryFeedback(.selection, trigger: isPlaying)
}
}
Design note: never animate a transport control for longer than 200 ms. Users tap play and immediately look away. A slow morph makes them wonder whether the tap registered.

3. iOS Settings: the toggle that snaps
What actually happens
Three things animate together: the knob translates, the track colour fills from the leading edge, and the knob stretches horizontally by a few points mid travel before rounding back. That stretch is the detail almost every custom toggle forgets. Much the same conclusion turns up on nngroup.com.
SwiftUI recreation
struct SnapToggle: View {
@State private var isOn = false
var body: some View {
Capsule()
.fill(isOn ? Color.green : Color(.systemGray4))
.frame(width: 51, height: 31)
.overlay(alignment: isOn ? .trailing : .leading) {
Circle()
.fill(.white)
.padding(2)
.shadow(radius: 1, y: 1)
}
.onTapGesture {
withAnimation(.spring(response: 0.22, dampingFraction: 0.75)) {
isOn.toggle()
}
}
.sensoryFeedback(.impact(weight: .light), trigger: isOn)
}
}
4. Mail: swipe to archive with a threshold
What actually happens
The row follows your finger one to one. At roughly 25 percent of the row width, the action icon locks in and a rigid impact haptic fires. That haptic is the real micro interaction: it tells your thumb “let go now and it will work” without you having to read anything.
SwiftUI recreation
struct SwipeRow: View {
@State private var offset: CGFloat = 0
@State private var armed = false
var body: some View {
RowContent()
.offset(x: offset)
.gesture(
DragGesture()
.onChanged { value in
offset = min(0, value.translation.width)
let shouldArm = offset < -90
if shouldArm != armed { armed = shouldArm }
}
.onEnded { _ in
withAnimation(.spring(response: 0.3, dampingFraction: 0.85)) {
offset = armed ? -400 : 0
}
}
)
.sensoryFeedback(.impact(flexibility: .rigid), trigger: armed)
}
}
Accessibility reminder: every swipe action must also exist in a visible menu. Gesture-only destructive actions are an accessibility failure, not a clever interaction.
5. Pull to refresh: the most copied micro interaction in mobile
What actually happens
Three distinct states, and strong implementations make all three visually different:
- Pulling: indicator scales and rotates proportionally to drag distance (the user is in control).
- Armed: indicator reaches full opacity, light haptic fires.
- Refreshing: indicator detaches and loops at a constant 1200 ms rhythm (the system is now in control).
SwiftUI recreation
List(items) { item in
Row(item: item)
}
.refreshable {
await viewModel.reload()
}
The native modifier gives you correct physics for free. Build a custom one only if the refresh itself is part of your brand story, and even then keep the same three states.

6. App Store: the tab bar icon bounce
What actually happens
On selection, the SF Symbol performs a short bounce (roughly 350 ms) while the label and tint cross-fade. The bounce only plays on a new selection, not when you tap the tab you are already on, where instead the list scrolls to top.
Image(systemName: "star.fill")
.symbolEffect(.bounce, value: selectedTab)
.foregroundStyle(selectedTab == .favourites ? .accentColor : .secondary)
Pitfall: bouncing all five icons at once. Animate only the one that changed state. Simultaneous motion destroys the hierarchy of attention. There’s a good explainer over at uxcel.com.
7. Apple Pay: the self drawing success checkmark
What actually happens
The circle scales up with a spring, the checkmark path strokes itself over roughly 350 ms, and a success notification haptic fires as the stroke completes. The haptic is what converts an animation into confirmation.
SwiftUI recreation
struct SuccessCheck: View {
@State private var done = false
var body: some View {
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 72))
.foregroundStyle(.green)
.scaleEffect(done ? 1 : 0.6)
.opacity(done ? 1 : 0)
.onAppear {
withAnimation(.spring(response: 0.45, dampingFraction: 0.6)) {
done = true
}
}
.sensoryFeedback(.success, trigger: done)
}
}
Rule: reserve the success notification haptic for moments with real consequences, such as payments, bookings and submissions. If a “saved” toast uses the same haptic as a 400 euro transaction, the signal is dead.
8. The error shake on a login field
What actually happens
A horizontal oscillation of about 8 to 10 points, decaying across three cycles in roughly 400 ms, paired with an error notification haptic and a colour shift on the field border. It mimics a head shake, which is why it reads as “no” in every culture that uses the gesture.
SwiftUI recreation
TextField("Password", text: $password)
.phaseAnimator([0, -10, 10, -6, 6, 0], trigger: attempts) { view, offset in
view.offset(x: offset)
} animation: { _ in
.linear(duration: 0.07)
}
.sensoryFeedback(.error, trigger: attempts)
The shake replaces nothing. You still need a readable error message underneath. Motion points at the problem, text explains it.

9. Duolingo and Activity Rings: progress fill with overshoot
What actually happens
The bar or ring does not jump to the new value. It animates over roughly 700 ms with a slight overshoot, and milestone moments add a soft impact haptic plus a one-off scale pulse. Progress animations are the only place where a longer duration is justified, because the duration itself communicates effort.
ProgressView(value: progress)
.progressViewStyle(.linear)
.animation(.spring(response: 0.7, dampingFraction: 0.65), value: progress)
.sensoryFeedback(.impact(weight: .light), trigger: milestoneReached)
10. Photos and Messages: the long press lift
What actually happens
Two phases people rarely separate:
- Press down (0 to 200 ms): the item scales down to about 0.96. This is a promise that something is coming.
- Lift (around 300 ms): the item scales up past 1.0, a shadow appears, the background blurs, and a medium impact fires at the exact moment the menu commits.
Thumbnail()
.scaleEffect(isPressing ? 0.96 : 1)
.animation(.easeOut(duration: 0.2), value: isPressing)
.onLongPressGesture(minimumDuration: 0.35) {
showMenu = true
} onPressingChanged: { pressing in
isPressing = pressing
}
.sensoryFeedback(.impact(weight: .medium), trigger: showMenu)
Haptics cheat sheet for iOS
| SwiftUI feedback | Use it for | Do not use it for |
|---|---|---|
| .selection | Pickers, segmented controls, tab changes | Confirmations |
| .impact(weight: .light) | Toggles, snapping, small state changes | Errors |
| .impact(weight: .medium) | Likes, lifts, drag pick up | Scroll events |
| .impact(flexibility: .rigid) | Threshold crossed, snap to grid | Repeated continuous drag |
| .success / .warning / .error | Task outcomes with consequences | Routine UI feedback |
Hard limit: no more than one haptic per user action, and no haptics inside a scroll loop. Battery drain and irritation both scale faster than delight.

When micro interactions start hurting usability
The most useful thing we can share after auditing dozens of iOS products is that over-animation is now a bigger problem than under-animation. Watch for these five failure modes:
- Animation on the critical path. If a 600 ms transition sits between a tap and a result the user needs, you have added 600 ms of latency to every single session. Measure it against your task completion times.
- Competing motion. Two or more animated elements on screen at once split attention and nothing feels emphasised. One hero motion per screen state.
- Decorative motion on repeat tasks. A celebration is wonderful the first time and insulting the fiftieth. Gate celebrations behind milestones, not behind every action.
- Motion that replaces copy. A shake is not an error message. A pulse is not an explanation. Motion directs attention, text carries meaning.
- Ignoring Reduce Motion. This is the one that turns a nice interface into an inaccessible one, and for some users it triggers genuine nausea or vestibular symptoms.
Respecting Reduce Motion in SwiftUI
struct AnimatedCard: View {
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@State private var isVisible = false
var body: some View {
CardContent()
.scaleEffect(isVisible ? 1 : (reduceMotion ? 1 : 0.9))
.opacity(isVisible ? 1 : 0)
.onAppear {
withAnimation(reduceMotion ? .easeOut(duration: 0.15)
: .spring(response: 0.4, dampingFraction: 0.7)) {
isVisible = true
}
}
}
}
Notice that Reduce Motion does not mean no feedback. Replace movement with a simple opacity change so the user still gets confirmation that state changed.
A 7 point checklist before you ship any micro interaction
- Does it answer a user question such as “did that work?”, “where did it go?” or “what happens if I keep pulling?”
- Does the first visible response happen in under 100 ms?
- Is the total duration inside the timing budget for its category?
- Is there exactly one haptic, and is its weight proportional to the consequence?
- Does it still make sense on the 500th repetition?
- Is there a Reduce Motion variant?
- Does it survive being tested on an older device without dropping frames?
If an interaction fails three or more of these, it is decoration. Remove it and spend the engineering time on the ones that carry real information.
FAQ
Can you provide some examples of micro interactions?
Yes: a button that compresses and springs back on tap, a toggle switch that slides and changes colour, a pull to refresh spinner, a heart that bursts on a double tap, a progress bar that fills with a small overshoot, an input field that shakes on an invalid password, a tab icon that bounces when selected, and a checkmark that draws itself after a successful payment. All ten patterns in this article are production examples taken from widely used iPhone apps.
What is the difference between a micro interaction and an animation?
An animation is one possible form of feedback. A micro interaction is the complete loop: trigger, rules, feedback and modes. You can have a micro interaction with no animation at all, for example a haptic tick when a picker snaps to a new value.
How long should a micro interaction last?
Between 80 and 400 ms for the vast majority of cases. Tap feedback should sit around 100 ms, state changes around 200 to 250 ms, and larger element transitions around 300 to 400 ms. Only progress and celebration moments justify going past half a second.
Do micro interactions actually improve conversion?
Indirectly. They reduce uncertainty, which reduces repeated taps, abandoned forms and support tickets. The ones that measurably move numbers are the functional ones: clear loading states, visible threshold feedback during swipes, and unambiguous success or error confirmations. Purely decorative motion rarely moves a conversion metric and sometimes lowers it by adding perceived latency.
Should I build micro interactions in SwiftUI or use Lottie?
Use SwiftUI for anything tied to user input or state, because it can be interrupted mid flight and follow a finger. Reserve Lottie or Rive for complex illustrated moments such as onboarding mascots or empty states, where the animation is linear and not interactive.
How many micro interactions should one screen have?
One primary motion per state change, plus passive feedback such as haptics and highlights. If a user can see three animated elements at the same time without having triggered them, the screen is too busy.
Bringing it into your own product
Pick one screen in your app this week, ideally the one with the highest drop off. Audit every tap, swipe and state change against the four part model and the timing table above. In most products you will find two or three moments where the interface goes silent at exactly the point the user needs reassurance. Fix those first. That is where the entire perception of quality lives.
At irisapp.cc we treat micro interactions as part of the product spec rather than a polish phase, because the timing and haptic decisions are far cheaper to make before the architecture is locked in than after.

