Navigate
Search topics across all sections
HTML Overlay
Imagine wearing augmented reality glasses. You look at a building and a floating info card appears next to it -- the name, the address, a star rating. You turn your head, the label follows the building perfectly. That is exactly what the Html component does: it pins real HTML content to 3D positions in your scene.
You place an Html component with a button inside your 3D scene. The button appears at the right position, but when you click it... nothing happens. The click seems to pass straight through the button into the canvas below.
onClick handler never fires. Pointer events are being captured by the canvas layer underneath the HTML overlay.
Real-world
Think of AR glasses. When you look at a real-world object through the lenses, you see digital labels floating next to it. The labels are not part of the physical world -- they are a separate layer projected onto your view. Move the object, and the label follows.
The Html component works the same way. Your 3D objects live on the WebGL canvas. Html creates a separate DOM layer on top and projects regular HTML elements to match 3D positions every frame. The HTML is not inside the 3D world -- it is a sticker on the glass that tracks what is behind it.
3D Object
A mesh in your scene
Html Component
Attach as child
DOM Overlay
Real HTML on top
AR Label!
Follows the object
Attaching Your First AR Label
The Html component from drei makes this surprisingly simple. Place it inside any mesh or group, and it will track that object's position automatically.
Step 1: Place Html inside a mesh
<mesh position={[2, 1, 0]}>
<sphereGeometry args={[0.5]} />
<meshStandardMaterial color="tomato" />
<Html center>
<div className="label">This is a sphere</div>
</Html>
</mesh>By nesting Html inside the mesh, the label automatically follows wherever the mesh goes. The center prop keeps the label centered on the object rather than anchored to its top-left corner.
Step 2: Make it feel embedded with transform
<Html transform distanceFactor={10}>
<div className="panel">
<h3>Info Panel</h3>
<p>Scales with the scene</p>
</div>
</Html>Without transform, the label stays a fixed pixel size no matter how far the camera is. With transform, it scales and rotates with the 3D perspective -- like a card that is actually in the scene rather than taped to your glasses.
Step 3: Add interactivity and occlusion
<Html
center
occlude={[wallRef]}
style={{ pointerEvents: 'auto' }}
>
<button onClick={handleClick}>
Click me
</button>
</Html>Two crucial extras: pointerEvents lets clicks reach your HTML elements, and occlude hides the label when it goes behind a 3D object. Without occlude, labels awkwardly float on top of walls they should be hidden behind.
What you just learned
Html creates real DOM elements projected onto 3D positions -- like AR labels on glasses.
Nest Html inside a mesh or group and it automatically tracks that object's position.
Use the transform prop to make labels scale and rotate with the 3D scene perspective.
Add style={{ pointerEvents: 'auto' }} to make buttons and inputs clickable inside Html.
Use the occlude prop to hide labels behind 3D objects for realistic depth behavior.
Question
If Html creates real DOM elements, does that mean you can use any React library inside it? What about a chart library, a form, or even a video player?
Think about it...
You want to add a tooltip to 200 trees in a forest scene. Each tree should show its species name on hover. Which approach would you use?
Hint: Think about how many DOM elements the browser can update at 60fps...
Try These Challenges
Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.
Try This!
Beginner
Toggle showLabels off — labels vanish
Try This!
Beginner
Set sphereSize to 1 — big planets
Try This!
Beginner
Max bobSpeed — bouncy spheres
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
<Html position={[0, 1.5, 0]} center>
<button onClick={() => console.log('clicked')}>
Click me
</button>
</Html><Html position={[0, 1, 0]}>
<div className="info-panel">
<h3>Product Details</h3>
</div>
</Html><mesh ref={wallRef}>
<boxGeometry args={[4, 4, 0.2]} />
<meshStandardMaterial />
</mesh>
<Html position={[0, 0, -2]}>
<p>I should be hidden behind the wall</p>
</Html>Best Practices
Limit Html Count
Each Html component creates a DOM element updated every frame. Keep the total under 20 to avoid layout thrashing. For many labels, use the flat Text component or a shared Html pattern.
Use wrapperClass
Style the outer positioning div with wrapperClass instead of inline styles. This keeps your CSS organized and avoids unnecessary object recreation on re-renders.
Always Occlude
Use the occlude prop whenever labels should hide behind objects. Without it, labels float on top of everything, which breaks the spatial illusion completely.
Set Pointer Events Explicitly
Always add pointerEvents: 'auto' on interactive Html elements. Without it, clicks pass through to the canvas and your buttons silently do nothing.