| name | visionos-design-guidelines |
| description | Apple Human Interface Guidelines for visionOS (Apple Vision Pro). Use when designing or building spatial apps: (1) windows, volumes, and immersive spaces, (2) ornaments, glass material, and spatial layout, (3) eye/hand input, hover effects, and target sizes, (4) comfort, motion, and immersion levels, (5) porting an iOS/iPadOS app to visionOS, (6) RealityKit + SwiftUI scene composition. Companion to the ehmo *-design-guidelines skills for the other Apple platforms. |
| license | MIT |
| metadata | {"author":"ulk","version":"1.0","source":"ulk (interne) — synthèse des Apple HIG visionOS (developer.apple.com/design/human-interface-guidelines/designing-for-visionos), comble le trou de la famille ehmo/platform-design-skills","source-version":"visionos-26","maintainer":"izo"} |
visionOS Design Guidelines (HIG)
Scope: native design conventions for visionOS apps — scene types, spatial layout, materials, input, comfort, immersion. Code-level SwiftUI/RealityKit patterns only where they encode a design decision.
Out of scope: general SwiftUI API modernity (→ swift-swiftui-pro), ARKit tracking internals, game-engine content pipelines.
The Spatial Mental Model
- Start windowed, earn immersion. The default and correct starting point is a familiar SwiftUI window floating in the person's space. Immersion is a progression the user opts into, not a launch state.
- Three scene types, one decision:
| Scene | What it is | Use when |
|---|
WindowGroup | 2D plane with glass background | Default. Content that would be an iPad app |
WindowGroup(style: .volumetric) | Bounded 3D volume, viewable from any angle | A 3D object is the content (model, board game, anatomy) |
ImmersiveSpace | Unbounded; app content surrounds the user | Environments, guided experiences — always user-initiated |
- Immersion levels within
ImmersiveSpace: .mixed (content coexists with passthrough — default), .progressive (Digital Crown dials immersion), .full (total environment). Prefer .mixed/.progressive; .full must justify itself and always offer an obvious exit.
Core Rules (non-negotiable)
- People look to target and pinch to act. Eye + indirect pinch is the primary input. Consequences: every interactive element needs a hover effect (automatic on standard controls;
.hoverEffect() on custom ones — its absence makes controls feel dead), and there is no cursor to hunt with.
- Minimum target: 60×60 pt with ~16 pt spacing between targets (vs 44 pt on iOS). Small, dense controls are the #1 ported-app failure.
- Glass, not opaque. Windows use the system glass material; never paint full opaque backgrounds that block passthrough. Use vibrancy-aware colors (
.primary/.secondary, materials) — hardcoded dark/light hex backgrounds break in every lighting condition. There is no dark/light mode on visionOS; the environment is the mode.
- Ornaments for secondary controls. Toolbars, tab bars, and playback controls belong in ornaments (
.ornament(attachmentAnchor:)) attached outside the window's edges — not in cramped in-window chrome. The system TabView already renders as a leading-edge ornament.
- Never move content into the user's personal space. Content appearing closer than ~0.5 m or approaching the user uninvited is threatening. Let people walk to content or reposition it with the window bar.
- Respect the comfort zone: primary content within a comfortable field of view slightly below eye level, 1–2 m away by default; avoid requiring head turns > ~45° for essential UI; nothing anchored to the user's head (except deliberate, subtle HUD elements — and even then prefer lazy-follow).
- Motion discipline: no sustained oscillation, no moving the horizon, no accelerating the world around the user (that is self-motion = nausea). Fades and short transforms over large sweeping animation. Honor Reduce Motion.
- Scale: use dynamic scale (system-managed, windows keep apparent size) for UI; fixed scale only for true-size objects (furniture, products). Text: system Dynamic Type styles are already tuned for depth — don't shrink type to fit more in.
- Depth is hierarchy, not decoration. Use subtle z-offsets (
ZStack + offset(z:), .padding3D) to lift the focused/primary element; avoid stacking many floating layers that cast confusing shadows.
- Shared space citizenship: in
.mixed contexts your app coexists with others — don't claim the whole field of view, don't spawn windows programmatically without user action, and persist window placement expectations (the system restores where the user put things).
- Privacy is structural: apps never receive raw eye-tracking data (hover effects render out-of-process) or camera passthrough. Don't design features that assume access to where the user looks or what they see.
Canonical Patterns
App structure with opt-in immersion
@main
struct SpatialApp: App {
var body: some Scene {
WindowGroup { BrowserView() }
WindowGroup(id: "model-viewer") { ModelVolume() }
.windowStyle(.volumetric)
.defaultSize(width: 0.6, height: 0.6, depth: 0.6, in: .meters)
ImmersiveSpace(id: "environment") { EnvironmentView() }
.immersionStyle(selection: .constant(.progressive), in: .mixed, .progressive, .full)
}
}
Ornament for secondary controls
ContentView()
.ornament(attachmentAnchor: .scene(.bottom)) {
PlaybackControls()
.glassBackgroundEffect()
}
Custom control done right
Button(action: select) { PosterCard(item: item) }
.buttonStyle(.plain)
.hoverEffect()
.frame(minWidth: 60, minHeight: 60)
RealityKit content in SwiftUI
RealityView { content in … } update: { … } embeds entities in windows/volumes/spaces; use Attachments to place SwiftUI UI on/near 3D entities instead of building text in 3D geometry.
Porting iOS/iPadOS Apps — Review Checklist
Common AI Mistakes
| ❌ Generated pattern | Why it fails | ✅ Correct |
|---|
Launching into ImmersiveSpace | Disorienting, HIG violation | Start windowed; user opts into immersion |
Color.black/.white window backgrounds | Blocks passthrough, fights glass | Default glass + vibrant materials |
| 44 pt targets, dense iOS layouts | Eye targeting misses | 60 pt minimum, generous spacing |
Custom tappable views without .hoverEffect() | No look-feedback → feels broken | Add hover effect to every custom target |
| In-window bottom toolbars | Cramped, occludes content | Ornaments outside window edges |
| Head-anchored UI following gaze rigidly | Nausea, claustrophobia | World-anchored; lazy-follow only if essential |
| Dark mode / light mode conditionals | No such modes on visionOS | Semantic colors + materials |
| Animating the environment around the user | Perceived self-motion → sickness | Move objects, never the world; fade transitions |
Sources
- Apple HIG — Designing for visionOS, Ornaments, Immersive experiences, Materials, Eyes, Gestures, Spatial layout (developer.apple.com/design/human-interface-guidelines)
- WWDC23 "Principles of spatial design" · WWDC23 "Design for spatial input" · WWDC24 "Design great visionOS apps"