Skip to content

HUD stacking in WebXR

S edited this page Aug 20, 2026 · 3 revisions

HUD stacking in WebXR

WebXR's DOM overlay feature lets you keep your interface in HTML during an immersive session. It is genuinely useful — real buttons, real text rendering, real accessibility — and it behaves differently enough from ordinary web layout to catch everyone once.

What DOM overlay actually is

During an immersive-ar session with DOM overlay, the browser composites a designated DOM element over the camera feed and your rendered 3D. The element is still the DOM: CSS applies, events fire, your framework renders normally.

But the surrounding assumptions have changed:

  • There is one overlay root. Not one per component. Everything visible during the session must be inside that subtree.
  • The compositor decides stacking between the overlay and the 3D content. Your z-index orders elements within the overlay; it says nothing about the relationship between the overlay and the scene.
  • The viewport is not the page. Notches, system UI and browser chrome behave differently in an immersive session than in the page you tested at your desk.
  • Pointer events are contested. The same tap can plausibly mean "press this button" and "place an object on that surface". Something has to arbitrate, and by default the DOM wins where the DOM is.

The failure that always happens

A button lands somewhere the user must touch to interact with the world.

Typically it is a control panel occupying the middle of the screen, or a transparent container that is invisible but still swallows taps across a large area. The AR interaction stops working and the cause is invisible — the element you cannot see is the one eating the event.

There is a second, worse version of the same mistake, and it is the one this project actually hit: the element is not merely invisible, it is gone. Sibling HUD elements vanished the moment the session began, because the browser composites the overlay root's subtree and nothing else. Same root cause — something is in the wrong place in the DOM — and you find either of them only by suspecting the overlay first.

The convention that keeps it manageable

  • Everything visible in AR must be a DOM descendant of the overlay root you handed to the session. This is the rule, and it is not a z-index problem: the browser composites that element's subtree and nothing else, so a HUD element that is a sibling of the root simply vanishes when the session starts — while looking perfectly fine in the pre-AR layout you tested it in. Here it is enforced by a repo-level test, because it is invisible to every other kind of check.
  • A stated stacking order within that root, so components stop fighting over z-index — which governs the 2D arrangement inside the overlay and says nothing about the overlay's relationship to the 3D scene.
  • Reserve the centre. The middle of the screen belongs to the world. Put controls at the edges, where a hit-test tap is not going to land.
  • No invisible full-size containers. A wrapper with pointer-events: none and explicit pointer-events: auto on the actual interactive children is the difference between a HUD and a tap-absorbing sheet.
  • Test on a phone, in a session, outdoors. A desktop browser with a fake session does not reproduce the notch, the system bars, or the thumb.

Choosing HUD or 3D

Not everything belongs in the DOM. The rule that has held up here:

  • DOM overlay for anything text-heavy, interactive or accessible — buttons, status lines, sliders, error messages. Crisp text and real focus handling are worth a lot.
  • 3D scene for anything that must relate to the world — an arrow pointing at an off-screen target, a ring around a waypoint, a distance label that should feel attached to a thing rather than to the glass.

The wayfinding HUD in this project is the second kind on purpose: edge arrows and rings are computed from the geometry of where targets actually are, so they belong in the scene where that geometry lives, not in a DOM layer that would have to be told about it.

A note on testing

The reason to keep the overlay's rules explicit is that the failures are invisible in the artefacts you normally check. A screenshot of a broken overlay looks fine — the tap-eating element is transparent. What catches it is a test that asserts where interactive elements are, or a human on a phone outdoors, which remains the only complete test for an outdoor AR interface.

Clone this wiki locally