Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2026 Spatial ComputingSafari & Web

WWDC26 · 16 min · Spatial Computing / Safari & Web

Get started with the HTML Model Element

Learn how the model element brings interactive 3D content to your websites — now on iOS, iPadOS, macOS, and visionOS. Discover tools for creating and optimizing 3D assets. Explore model element’s features and see how web standards are shaping the future of 3D on the web.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 10 snippets

Load a model xml · at 4:19 ↗
<!-- Using the src attribute -->
<model src="mallet.usdz"></model>

<!-- Using a <source> child for MIME type -->
<model>
    <source src="mallet.usdz" type="model/vnd.usdz+zip">
</model>
Image fallback xml · at 4:39 ↗
<model id="mallet" src="mallet.usdz">
    <img src="mallet.png"
         alt="Rubber mallet with wooden handle">
</model>
Ready promise xml · at 5:09 ↗
<model id="mallet" src="mallet.usdz"></model>

<script>
    const model = document.getElementById("mallet");
    model.ready.then(result => {
        // Hide the loading indicator
    }).catch(error => {
        // Loading failed, show fallback
    });
</script>
Polyfill fallback xml · at 5:39 ↗
<script type="module">
    if (!window.HTMLModelElement) {
        import("model-element-polyfill.js").then(() => {
            // Polyfill ready to use
        });
    }
</script>
Model background xml · at 6:13 ↗
<model id="mallet" src="mallet.usdz"></model>
<style>
    model {
        background-color: #f4f1ec;
    }
</style>
Stage mode xml · at 6:47 ↗
<model id="mallet"
       src="mallet.usdz"
       stagemode="orbit">
</model>
Custom transforms xml · at 7:31 ↗
<model id="boot" src="boot.usdz"></model>
<button id="button-side">Side</button>
<button id="button-reset">Reset</button>

<script>
    const model = document.getElementById("boot");
    const initialTransform = model.entityTransform;

    document.getElementById("button-side")
            .addEventListener("click", () => {
        const transform = new DOMMatrix();
        transform.rotateSelf(0, 135, 0);
        model.entityTransform = transform;
    });

    document.getElementById("button-reset")
            .addEventListener("click", () => {
        model.entityTransform = initialTransform;
    });
</script>
Transition animation xml · at 8:35 ↗
<script>
    const model = document.getElementById("boot");
    const duration = 500;
    let currentAngle = 0;
    let animationId = null;

    function animateTo(targetAngle) {
        if (animationId) cancelAnimationFrame(animationId);
        const startAngle = currentAngle;
        const startTime = performance.now();

        function step(now) {
            const progress = Math.min((now - startTime) / duration, 1);
            const ease = 1 - Math.pow(1 - progress, 3);
            currentAngle = startAngle + (targetAngle - startAngle) * ease;
            model.entityTransform = new DOMMatrix().rotateSelf(0, currentAngle, 0);
            if (progress < 1) animationId = requestAnimationFrame(step);
        }

        requestAnimationFrame(step);
    }

    document.getElementById("button-side").addEventListener("click", () => animateTo(135));
    document.getElementById("button-reset").addEventListener("click", () => animateTo(0));
</script>
Animation playback xml · at 10:07 ↗
<model id="bottle" src="bottle.usdz"></model>
<button id="button-play" onclick="play(5)">
    Play
</button>
<button id="button-reverse" onclick="play(-5)">
    Reverse
</button>

<script>
    const model = document.getElementById("bottle");

    function play(rate) {
        model.playbackRate = rate;
        model.play();
    }
</script>
AR Quick Look xml · at 11:06 ↗
<a rel="ar" href="bottle.usdz">
    <model id="boot" src="bottle.usdz"></model>
</a>

Resources