iOS Context Menu Long-Press Blur in SwiftUI
Lift, blur, and menu share one spring and one source of truth. The recognizable feel lives in that coordination.
TL;DR
An iOS context menu coordinates three things from one long press: the pressed element lifts and stays sharp, the backdrop blurs and dims, and an action menu animates in anchored to the element. SwiftUI's native .contextMenu modifier delivers all three correctly, including the haptic, true backdrop blur, edge repositioning, and VoiceOver wiring, so it is the default and the preview: parameter covers most custom-preview wishes. Build a fully custom overlay only when the appearing surface is not an action menu, and then drive the lift scale, blur opacity, and menu entry from a single spring, since separate timelines are what make a clone feel wrong. Custom also means owning accessibility. A free VP0 design supplies the screens to wire it onto.
What is actually happening when a context menu opens?
Three coordinated things, and most hand-built clones nail one and miss the other two. The element you pressed lifts and stays sharp, everything behind it blurs and dims, and a menu animates in anchored to that element, all from a single long press. The recognizable iOS feel lives in the coordination: the lift, the blur, and the menu share one spring and one source of truth, so they move as a system rather than three separate animations racing each other.
The good news for SwiftUI builders is that the native contextMenu modifier gives you all three for free, correctly, including the platform behaviors that are genuinely hard to replicate: the haptic on trigger, the blur of the live backdrop, the menu repositioning near screen edges, and the accessibility wiring. The first real decision is whether you even need to build a custom one.
When should you use the native modifier versus build your own?
| Approach | What you get | Use when |
|---|---|---|
.contextMenu { } | Full native behavior, zero custom code | The menu is a list of actions; this is most cases |
.contextMenu + preview: | Native menu, custom lifted preview | You want a rich preview card above the menu |
| Fully custom overlay | Total control of layout and content | The “menu” is really a custom panel, not actions |
The honest default is the native modifier, and reaching past it is the common mistake: developers rebuild the whole interaction to get one custom touch they could have had with the preview: parameter. Build fully custom only when the surface that appears is not a menu of actions at all (a reaction picker, a mini-editor, a custom card), because then you genuinely need to own the layout, and you are signing up to reimplement the blur, the spring, the edge handling, and the a11y yourself.
How do you build the custom version correctly?
Three layers in a full-screen overlay, driven by one animation. When the long press fires (a LongPressGesture, ~0.5s, with a haptic via UIImpactFeedbackGenerator on success), you present an overlay containing:
- The blurred backdrop: a snapshot or a live
.backgroundmaterial with.ultraThinMaterialover a dimming layer, tapping it dismisses. - The lifted element: the same view, rendered above the blur, scaled up about 5% on a spring so it reads as picked up. Matching its frame to the original is the detail that sells the lift, so measure the source with a geometry reader and animate from exactly there.
- The menu, anchored to the lifted element and flipping above or below it depending on proximity to the screen edge.
The single spring is the whole trick: one withAnimation(.spring(response: 0.35, dampingFraction: 0.8)) drives the scale, the blur’s opacity, and the menu’s entry together. Animate them on separate timelines and the lift finishes before the blur arrives, which is the exact wrongness that makes a custom menu feel off. The same one-spring-many-properties discipline runs through the iMessage bubble physics and the hero shared-element transition, where coordinated motion is the whole effect.
The blur itself has a quality cliff: a true backdrop blur (sampling what is behind) looks right, while a flat semi-transparent gray does not, and on older devices a full live blur can stutter, so a captured-snapshot-then-blur approach is the performance-safe fallback that still reads correctly.
What completes the illusion?
Honesty about the press and respect for the platform. The element should give immediate feedback on touch-down (a tiny scale-down before the menu, so the press feels acknowledged during the half-second hold), and the menu items follow the system grammar: destructive actions in red, an SF Symbol per row, dividers grouping related actions, and a disabled state that dims rather than hides. Apple’s context-menu guidance is explicit that these are predictable affordances, not a place for novelty.
Accessibility is where custom builds quietly fail: the native modifier exposes the menu to VoiceOver and supports the Accessibility long-press timing automatically, so a custom version must add an alternative trigger (the menu cannot be long-press-only for users who cannot long-press), announce the menu’s appearance, and respect Reduce Motion by cross-fading instead of springing. If you cannot commit to that, the native modifier is not just easier, it is more correct.
For the screens these menus live on, lists, grids, photo galleries, a free VP0 design supplies the structure, so an agent wires .contextMenu (or the custom overlay where genuinely needed) onto real layouts instead of inventing the interaction from scratch.
Key takeaways: the iOS context menu in SwiftUI
- Use
.contextMenuby default: it gives the lift, blur, haptic, edge handling, and a11y for free; thepreview:parameter covers most “but I want a custom preview” cases. - One spring drives all three layers: lift, blur, and menu share a single animation or the interaction feels broken.
- A true backdrop blur is non-negotiable; snapshot-then-blur is the performance fallback, flat gray is wrong.
- Match the lifted frame to the source with a geometry reader so the pickup reads as continuous.
- Custom means owning accessibility: an alternative trigger, announcement, and Reduce Motion, or stay native.
Frequently asked questions
How do I build an iOS context menu with long-press blur in SwiftUI? For action menus, use the native .contextMenu modifier, which provides the lift, backdrop blur, haptic, edge repositioning, and accessibility automatically; add the preview: parameter for a custom lifted card. Build a fully custom overlay only when the surface is not an action menu, driving the lift, blur, and menu from one spring. A free VP0 design supplies the list and grid screens to wire it onto.
Should I use SwiftUI’s contextMenu or build a custom one? Default to the native modifier: it handles the hard parts (true blur, haptics, edge cases, VoiceOver) correctly. Build custom only when the appearing surface is a reaction picker, mini-editor, or card rather than a list of actions, since only then do you actually need to own the layout.
Why does my custom context menu feel off? Usually the layers animate on separate timelines, so the lift completes before the blur and menu arrive. Drive the scale, blur opacity, and menu entry from a single withAnimation spring, and match the lifted element’s frame to the original measured position.
How do I get the real iOS blur effect? Use a true backdrop blur that samples what is behind the menu (.ultraThinMaterial over a dimming layer), not a flat semi-transparent overlay. On older devices where a live blur stutters, snapshot the background and blur the snapshot, which preserves the look at a stable frame rate.
Do custom context menus need accessibility work? Yes, and it is where they usually fail. The native modifier exposes the menu to VoiceOver and supports accessibility timing automatically; a custom build must add a non-long-press trigger, announce the menu, and honor Reduce Motion, or it excludes users the native version would have included.
What VP0 builders also ask
How do I build an iOS context menu with long-press blur in SwiftUI?
For action menus, use the native .contextMenu modifier, which provides the lift, backdrop blur, haptic, edge repositioning, and accessibility automatically, with the preview: parameter for a custom lifted card. Build a fully custom overlay only when the surface is not an action menu, driving lift, blur, and menu from one spring. A free VP0 design supplies the list and grid screens.
Should I use SwiftUI's contextMenu or build my own?
Default to the native modifier: it handles true blur, haptics, edge cases, and VoiceOver correctly. Build custom only when the appearing surface is a reaction picker, mini-editor, or card rather than a list of actions, since only then do you genuinely need to own the layout.
Why does my custom context menu feel off?
Usually the layers animate on separate timelines, so the lift completes before the blur and menu arrive. Drive the scale, blur opacity, and menu entry from a single withAnimation spring, and match the lifted element's frame to the original measured position with a geometry reader.
How do I get the real iOS context menu blur?
Use a true backdrop blur sampling what is behind the menu (.ultraThinMaterial over a dimming layer), not a flat semi-transparent overlay. On older devices where a live blur stutters, snapshot the background and blur the snapshot to keep the look at a stable frame rate.
Do custom context menus need accessibility work?
Yes, and it is where they usually fail. The native modifier exposes the menu to VoiceOver and supports accessibility timing automatically; a custom build must add a non-long-press trigger, announce the menu's appearance, and honor Reduce Motion, or it excludes users the native version included.
Part of the Native Apple & SwiftUI: The iOS Ecosystem hub. Browse all VP0 topics →
Keep reading
iMessage Reply Bubble Physics in SwiftUI: The Real Spring
Animate scale and offset not the frame, one restrained spring with sent springier than received, and a tail only on the last bubble of a run.
Interactive Solar System 3D Viewer in SwiftUI
RealityView entities, orbits as pivot rotations, NASA textures, and the scale cheat you must pick on purpose: the 3D solar system that stays smooth.
Loyalty Punch-Card Stamp Animation in SwiftUI
The stamp is the product: a spring with overshoot, a resting tilt, and a haptic thunk, on a count the server owns and the merchant grants.
ADHD Daily Routine Planner UI in SwiftUI, Free
Build an ADHD-friendly daily routine planner in SwiftUI from a free template. Visual, low-friction, time-aware design with Claude Code or Cursor.
Build a Stock Market Heat Map Grid UI in SwiftUI
A market heat map colors and sizes tiles by gain and market cap. Here is how to build the stock market heat map grid in SwiftUI, with an accessible color scale.
Build a Booking.com-Style Availability Calendar in SwiftUI
A Booking.com-style availability picker is more than a date picker. Here is how to build the availability calendar in SwiftUI, with real open and booked dates.