Explore immersive website environments in visionOS
Use Safari's new Immersive API on visionOS to turn HTML model elements into real-world-scale website environments with video, animation, and optimized USDZ assets.
TL;DR
- The Immersive API lets a website request an immersive presentation from an HTML
<model>element usingrequestImmersive(), while keeping the webpage visible in Safari. - Inline previews can reuse the same USDZ environment by controlling
entityTransformwithDOMMatrixto show a seat- or location-specific point of view before entering immersion. - Immersive environments can combine with the Fullscreen API for docked video, model animations, RealityKit annotations, and Safari window shadows.
- Performance depends heavily on asset preparation: reduce vertices and entities, use low-poly meshes for shadow receiving, bake lighting where possible, and compress USDZ files with
usdcrush.
Immersive API basics
The session introduces website environments for Apple Vision Pro using Safari on visionOS. The core building block is the HTML <model> element, which displays a USDZ model on a webpage and can optionally use an environment map for lighting and reflections.
The new JavaScript Immersive API follows the same general pattern as the Fullscreen API: feature detection, a request method, state inspection, change and error events, and CSS support via an immersive pseudo-class. Unlike fullscreen, immersive presentation moves the model beyond the browser bounds while the webpage remains visible.
- Use
<model src="...">for the 3D asset andenvironmentmap="..."for lighting. - Call
requestImmersive()on a model element in response to a user interaction. - Use
document.immersiveEnabledto decide whether to show immersive UI. - Use
document.immersiveElementandimmersivechangeto keep page state synchronized.
Basic model element with environment lighting
Loads a USDZ model and an HDR environment map for scene lighting.
<model src="teapot.usdz" environmentmap="kitchen.hdr"> </model>Request immersive presentation
Requests the immersive transition from a user-initiated button click.
immersiveButton.addEventListener("click", async () => {
await model.requestImmersive();
});Inline environment previews
The ticket sales example starts with an inline preview of a theater environment. The same theater USDZ is embedded in the page, then transformed so the preview shows the view from a selected seat rather than the default fitted exterior view.
By waiting for the model to be ready and assigning entityTransform, the page can control the model's position, rotation, and scale. Seat data can be stored separately, with each seat mapped to a translation and orientation in the web's right-handed, Y-up coordinate system.
- The default inline model behavior scales the model to fit the element bounds, which is often not appropriate for an interior environment preview.
- Setting an identity
DOMMatrixremoves the default fitting transform. - Translating the environment downward can place the virtual camera at seated eye level for inline preview.
- Applying the inverse of a seat's position and orientation makes the inline preview match the selected seat.
Add the environment model inline
Embeds the theater USDZ and lighting map inside the ticket page for an inline preview.
<div class="seat-preview">
<model id="theater" src="theater-model.usdz" environmentmap="theater-lighting.hdr"> </model>
</div>Build a seat-specific inline transform
Constructs a transform that shows the model from the selected seat at seated eye level.
function buildTransform(seat) {
const transform = new DOMMatrix();
const { x, y, z, ry } = seat;
// Rotate and translate the model to match
// the seat's origin and orientation
transform.rotateSelf(0, -ry, 0);
transform.translateSelf(-x, -y, -z);
// Translate the model down, for eye level preview
transform.translateSelf(0, -1.0, 0);
return transform;
}Entering and leaving immersion
Inline and immersive presentations use different reference frames. Inline, the model element's origin is at the center of the inline layer and scale follows CSS conventions. Immersive, the origin is at the person's feet on the floor and the model is rendered at real-world scale.
Because the environment opens behind Safari's window, the model may need a different transform when immersive so the main focus is not hidden by the page. UI should also provide a clear exit affordance, while still responding to dismissal via the Digital Crown by listening for state changes.
The escape-room example shows that an inline preview is optional. A hidden <model> can still be requested as immersive; when hidden with display: none, the heavy model is not downloaded or decoded until the immersive request occurs.
- Show immersive controls only when
document.immersiveEnabledis true. - Request immersion only from a user interaction.
- Update transforms and layout in response to
immersivechange, not just button clicks. - Use loading UI around
requestImmersive()when the model is not preloaded inline.
Detect support and update on immersive changes
Shows immersive UI only when supported and recomputes page/model state whenever immersion changes.
if (document.immersiveEnabled) {
immersiveButton.hidden = false;
}
theater.addEventListener("immersivechange", () => {
const isImmersive = !!document.immersiveElement;
const transform = buildTransform(currentSeat, isImmersive);
theater.entityTransform = transform;
document.body.classList.toggle("immersive", isImmersive);
});Hidden model requested as immersive
Keeps the model out of the inline page, defers loading until entry, and displays progress while the immersive request completes.
<model id="escapeRoom" src="escape-room.usdz" environmentmap="room-lighting.hdr" style="display: none"> </model>
enterButton.addEventListener("click", async () => {
showLoadingAnimation();
try {
await escapeRoom.requestImmersive();
} catch (error) {
console.log(error);
} finally {
hideLoadingAnimation();
}
});Video, animation, and shadows inside environments
The escape-room demo uses custom RealityKit annotations in the USDZ to mark a video docking region, such as a TV screen, and to support baked light spill around the docked video. The annotations can be authored with tools such as Reality Composer Pro or the provided Blender add-on.
Once the model contains a video docking region, requesting fullscreen on a video places it into the corresponding surface inside the immersive environment. Because the Immersive API and Fullscreen API can both be active, a website can show a fullscreen video while the user remains inside the environment.
Model animations exported with the USDZ can be triggered from JavaScript. The example waits for the video to end, exits fullscreen to undock the video, then plays the model animation that opens a door. Safari window shadows can be enabled by tagging receiving meshes with the Scene Understanding component; the session recommends using a dedicated low-poly mesh for that purpose.
- Use RealityKit annotations for nonstandard environment behaviors such as video docking and Scene Understanding shadow receivers.
- Use
requestFullscreen()on the video to dock it into the environment when the USDZ is annotated appropriately. - Use model animation playback APIs such as
play()and timeline control withcurrentTimefor staged environment changes. - Prefer low-poly shadow-receiving meshes because complex geometry can make shadow computation expensive.
Dock video with the Fullscreen API
Requests fullscreen video; with the right model annotations, Safari docks the video onto the tagged surface in the environment.
const trailerVideo = document.getElementById("trailerVideo");
const demoButton = document.getElementById("demoButton");
demoButton.addEventListener("click", async () => {
await trailerVideo.requestFullscreen();
});Play a model animation after video ends
Undocks the video and starts the exported model animation when playback finishes.
trailerVideo.addEventListener("ended", async () => {
await document.exitFullscreen();
escapeRoom.play();
});Performance and image controls
Immersive environment USDZ files are often larger and more complex than object models, so asset optimization directly affects download time, memory use, and rendering performance. The session emphasizes removing invisible geometry, merging entities where appropriate, using low-poly support meshes, simplifying shaders, and baking lighting into textures for unlit materials where possible.
The session also briefly highlights image controls as another small markup feature that can add spatial behavior. Adding controls to an image lets the browser provide platform-appropriate controls; on visionOS, panoramas and spatial photos can be viewed immersively.
- Reduce vertex count by not exporting geometry that cannot be seen from the intended origin.
- Reduce entity count by merging static objects that do not need to be separate.
- Bake lighting and shadows into textures when that lets you avoid expensive runtime shading.
- Compress USDZ textures with
usdcrushto reduce model size and improve loading time.
Compress a USDZ with usdcrush
Uses the command-line tool available on Mac to produce a smaller optimized USDZ.
usdcrush model.usdz -o optimized.usdzEnable browser image controls
Adds native image controls so visionOS can offer immersive viewing affordances for supported images.
<img src="panorama.jpg" controls>Resources
- Download - Immersive model add-on for Blender
- WebKit.org - Theater Ticket Sales immersive website environment demo for Apple Vision Pro
- WebKit.org - Escape Game immersive website demo for Apple Vision Pro
- GitHub: Spatial Backdrop explainer
- WebKit.org - Report issues to the WebKit open-source project
- Submit feedback
Related Sessions
- Design immersive environments for visionOS apps and the spatial web
- Get started with the HTML Model Element
- What's new in WebKit for Safari 27
- Optimize your custom environments for visionOS
- What's new for the spatial web
Rediscover the HTML select element
Use Customizable Select in Safari 27 to style native HTML select controls with CSS, rich option content, and accessible progressive enhancement.
Safari and Web Technologies Group Lab
Online WWDC26 group lab for asking Apple engineers about Safari and web technologies announcements and implementation guidance.