# HTML-in-Canvas API: Rendering Live DOM Elements as Canvas Textures


The HTML-in-Canvas API is an experimental web platform feature that bridges the HTML DOM and the Canvas element. It **allows you to take a live, interactive HTML element and render its current visual state onto a 2D or WebGL canvas,** where it can be used as a texture, distorted, animated, or composited into a scene.

The rendered element stays connected to the DOM. When its content updates, the canvas receives a `paint` event. When a user types into an `input` nested inside the canvas, that interaction is reflected in the canvas rendering.

<iframe width="100%" height="315" src="https://www.youtube.com/embed/1zfRSiZBLyQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## Current status

As of this writing, HTML-in-Canvas is experimental and only available behind a flag in Chromium-based browsers. It is not a web standard and should not be used in production. To try it in Chrome Canary, navigate to `chrome://flags`, search for "HTML-in-Canvas", enable the flag, and relaunch.

![The Chrome Experiments flag page showing the "HTML-in-Canvas" setting enabled](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/25539662-c6a5-4f3c-cd20-ce4e0f299000/public =1280x720)

## The problem it solves

HTML and Canvas have historically been separate tools with complementary strengths. HTML handles structured layouts, text rendering, accessibility, and built-in interactivity. Canvas handles custom graphics, GPU-accelerated performance, and effects like shaders and distortions that CSS cannot achieve.

The traditional workarounds for mixing them are unsatisfying. Overlaying HTML elements on top of a canvas with absolute positioning creates z-index and interaction problems. Recreating UI components using canvas drawing commands produces poor text rendering, no accessibility, and large amounts of boilerplate.

![A creative "dark pattern" where a user must play pinball to unsubscribe from promotional emails demonstrating a novel UI interaction](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/dd871423-a1fb-4160-ac5b-8a7832236d00/orig =1280x720)

HTML-in-Canvas removes the need for either workaround by letting you define UI with HTML and CSS and then use that element as source data for the canvas.

## The `layoutsubtree` attribute

The `layoutsubtree` boolean attribute on a `<canvas>` element changes how its child elements are treated. Without it, children of `<canvas>` are treated as fallback content and only displayed if the canvas itself is unsupported.

With `layoutsubtree`:

- Child elements are **not painted to the screen** in normal DOM flow. They become visually invisible.
- They are still fully processed in the **layout and accessibility tree**. Their size and position are calculated, they can receive focus, and they are visible to screen readers.

This is the core mechanism. The browser handles layout and accessibility, but defers painting, allowing canvas code to capture and use the rendered state.

```html
<canvas id="scene" layoutsubtree>
  <div id="board" class="timetable-styles">
    <!-- timetable content -->
  </div>
</canvas>
```

![Developer tools showing the invisible but layout-active HTML board element highlighted over the 3D scene](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/323477ff-a234-4ab2-0b44-106c196ab400/lg1x =1280x720)

## WebGL implementation

For a Three.js 3D scene, the HTML element becomes a WebGL texture updated via `gl.texElementImage2D()`.

```javascript
[label scene.js]
const boardElement = document.getElementById('board');
const canvas = document.getElementById('scene');

// Function to upload the element's current state to the GPU
function uploadBoardToTexture() {
  const gl = renderer.getContext();

  gl.bindTexture(gl.TEXTURE_2D, glTexture);

  gl.texElementImage2D(
    gl.TEXTURE_2D,
    0,
    gl.RGBA,
    gl.RGBA,
    gl.UNSIGNED_BYTE,
    boardElement  // the DOM node
  );
}

// Only re-upload when the HTML element has actually changed
canvas.addEventListener('paint', uploadBoardToTexture);

function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}
```

The final argument to `gl.texElementImage2D()` is a direct reference to the DOM node. The browser transfers the current visual state of that element, including all its text, styling, and children, to the GPU as a texture. The `paint` event fires whenever the source element changes, so the texture is only re-uploaded when needed rather than on every frame.

![The live HTML timetable correctly rendered on the 3D display board in the train station scene](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/6b472196-fe6b-4334-6ecc-590440f9c000/public =1280x720)

## 2D canvas implementation

For standard 2D canvases, the equivalent method is `ctx.drawElementImage()`. The API is simpler because there is no texture binding step.

![Basic 2D canvas example code from the official GitHub proposal showing its relative simplicity](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/27d7352a-03fe-4dd0-5038-92ef4afbda00/lg1x =1280x720)

```html
<canvas id="my-canvas" style="width: 400px; height: 200px;" layoutsubtree>
  <form id="form_element">
    <label for="name">name:</label>
    <input id="name">
  </form>
</canvas>

<script>
  const canvas = document.getElementById('my-canvas');
  const formElement = document.getElementById('form_element');
  const ctx = canvas.getContext('2d');

  canvas.onpaint = () => {
    ctx.reset();
    ctx.drawElementImage(formElement, 100, 0);
  };
</script>
```

The `onpaint` handler fires when the form changes, such as when the user types into the input. `ctx.drawElementImage()` takes the element and an x/y coordinate. The result is a live, interactive form element rendered at an arbitrary position on the 2D canvas.

## Privacy-preserving rendering

Any API that reads pixel data from rendered elements raises fingerprinting concerns. If system-specific rendering details like custom fonts, visited link colors, or OS themes were exposed through canvas pixel data, scripts could use that information to identify users.

![The "Privacy-preserving painting" section of the proposal listing sensitive information intentionally excluded from rendering](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/1a89291f-0b92-4435-2b41-be506460ad00/lg1x =1280x720)

The proposal addresses this with a privacy-preserving rendering mode that explicitly excludes: cross-origin content from `<iframe>` elements, system colors and themes, spelling and grammar markers, visited link appearance, and pending form autofill data. Elements rendered through this API omit these details, preventing fingerprinting while still exposing the structure and content of same-origin elements.

## Known limitations

The proposal documents several open issues. There is a one-frame delay in updates when using `drawElementImage`. Certain CSS properties, including scrollbars within rendered elements, can cause crashes. Performance has not been fully optimized; uploading complex or frequently updated elements to the GPU has cost, and the development team is actively working on this.

These are the categories of issues that get resolved during an experimental phase, but they mean the current implementation is not suitable for production use.

## Final thoughts

HTML-in-Canvas removes a long-standing constraint in web development. The **ability to use HTML and CSS for layout and accessibility while treating the result as a canvas-renderable resource opens up use cases** that were previously only possible through fragile workarounds or by giving up either accessibility or graphical flexibility.

The experimental status means the API and its ergonomics will change before standardization. For developers exploring it now, the 2D canvas path with `drawElementImage` is the simplest entry point. The WebGL path via `texElementImage2D` is more involved but enables the full range of GPU effects on live HTML content.

The specification and open issues are tracked in the [HTML-in-Canvas GitHub repository](https://github.com/nicowillis/html-in-canvas).