rshankras/spatial-design
Designing for visionOS — spatial layout and ergonomics (60pt eye targets, field-of-view placement, dynamic scale), eyes-and-hands input with hover rules, motion and visual comfort (the 0.2 Hz rule, vection, depth-cue agreement), immersion strategy, spatial sound, and the video-format decision guide. Use when designing or reviewing any visionOS app, window, volume, or immersive experience.
npx skills add https://github.com/rshankras/claude-code-apple-skills --skill spatial-design
The design rules that make a visionOS app comfortable instead of exhausting — distilled from
Apple's spatial-design sessions (WWDC23–26). Widget-specific rules live in visionos/widgets;
custom immersive-environment production and budgets live in immersive-environments.md.
Don't window a 2D app. Identify the one thing only possible on visionOS — the "key moment" —
and build around it. Immersion has three approaches, and full immersion is not mandatory:
full environments · integration with real surroundings (scene sensing) · meaningful audio.
easier than up/down. Center = primary content; edges = secondary, infrequent actions.
extended reading goes farther out, centered and slightly below eye line.
(they scale up as they move away). Never fixed scale for UI; keep custom UI facing the viewer.
back). Keep text flat; 3D text is hard to read at an angle.
lighting, and "can feel constricting." Elements on glass use vibrancy + white text/symbols;
system colors over custom (they're calibrated for legibility). Brand color goes in
backgrounds or whole buttons, not glyphs.
semibold), slightly increased tracking. Avoid small/lightweight custom fonts.
overlap the window's bottom edge by 20pt, borderless buttons inside · sheets appear
centered at the parent's Z while the parent dims and pushes back · nested corners concentric
(outer radius = inner + padding).
.back depth alignment — switch to .front whencontent should read toward the viewer; rotation3DLayout (not rotation3DEffect) when
rotation must affect layout; SpatialContainer/spatialOverlay for shared 3D space;
scaledToFit3D() for models.
element can be smaller if surrounding spacing makes up the difference. Standard buttons:
44pt + ≥8pt clearance; stacked buttons ≥16pt apart; list rows 4pt padding so hover
effects don't overlap.
center text/glyphs with generous padding.
(never read-only data). Hover runs out-of-process: gaze stays private until a gesture.
flicker (reveal effects: ~0.8s in / 0.2s out); effects start from a visible element — no
invisible hotspots; high-traffic controls (toolbars, table cells) use ONLY the standard
highlight; keep an anchoring element static; respect Reduce Motion (swap for cross-fade).
Custom gestures must be explainable, repeatable without fatigue, distinct from system
gestures, and false-positive-tested; map them to real-world actions.
direct touch is for up-close manipulation — compensate for missing tactility (raise buttons,
brighten as the finger approaches, snap state + spatial sound on contact).
controls should persist while looked at.
double vision and fatigue. Beware repeating patterns (eyes lock onto different repeats).
Make big moving content semitransparent so passthrough anchors the viewer.
semitransparency, and honor Reduce Motion with an oscillation-free alternative.
predictable focus of expansion inside the field of view; never fast pure rotations — cut
with a quick fade instead. Avoid close-range fly-bys.
stationary-first — the system fades immersive content when people move.
interactions only. Slow dark→bright transitions (allow adaptation).
looping ambient surround beds. Even a windowed app may fill the room with a soundscape.
audibly from the same spot.
multiple emitters to characters (feet omnidirectional, mouth directional).
Adapt to the real room (portal up to 4m across in big rooms; set-distance + dimmed passthrough
in tiny ones) · open small and welcoming, onboard that content reacts to people · use head
position for eye contact (proximity signals interactivity; averted attention signals not) ·
consistent interaction rules — inconsistency shatters immersion · pace emotion with low-relief
breaks (nonstop interaction physically exhausts) · gentle in-world boundaries over hard
failures · accessibility is spatial too: VoiceOver, Dwell, captions, audio descriptions,
Dynamic Type.
| You want | Use |
|---|---|
| Flat storytelling | 2D/3D/spatial video, windowed or docked (3D needs expanded) |
| "Being there," forward-facing | Stereo 180° (half-equirectangular) |
| Look-anywhere | 360° (equirectangular 2:1, typically mono) |
| Action-cam POV | Wide-FOV APMP (120–180°, parametric lens projection) |
| Premium cinematic immersion | Apple Immersive Video (8160×7200/eye @ 90fps, up to 210°×180°) |
Comfort rule: immersive playback puts the head where the camera was — high-motion scenes
auto-reduce immersion (QuickLook/AVKit/RealityKit); encode stereo as MV-HEVC, never
side-by-side. APMP formats never play inline.
Prefer scene restoration (windows come back where placed); disable only for transient/welcome
windows (restorationBehavior(.disabled), defaultLaunchBehavior(.suppressed)). Adapt
volumes to walls/tables via surface snapping info (authorization required). Handle Digital
Crown recentering (onWorldRecenter). Progressive immersion with an aspect ratio is a comfort
lever for vertical/high-motion content.
Spatial design review:
Area | Finding | Comfort/ergonomic rule violated | Fix
ordered: comfort violations (motion/depth/head-lock) first — they end sessions; then input
targets; then layout polish. Route environment production to immersive-environments.md,
widgets to visionos/widgets.
visionos/widgets, design/animation-patternsTake rshankras/spatial-design from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.