← Chrome 148 reference

v148 · origin trial · experimental

HTML-in-canvas

An experimental API that lets you draw live DOM elements — complete with interaction, accessibility, and browser-native styling — directly into a 2D canvas, WebGL texture, or WebGPU texture. Bridges the long-standing gap between the DOM's rich UI semantics and the canvas/WebGL performance model.

Origin Trial — Chrome 148–151 This API is in origin trial. Enable it for your users by registering for the origin trial token, or test locally via chrome://flags/#canvas-draw-element in Chrome 148+. The API shape may change before it ships.

at a glance

Origin trialChrome 148 – 150
ChromeStatus5172548013916160 — HTML-in-canvas (API record)
Spec / ExplainerWICG/html-in-canvas GitHub repository
Source: ChromeStatus 5172548013916160; WICG/html-in-canvas, May 2026.

why it exists

WebGL and canvas-based applications (games, design tools, video editors) have always faced a painful choice: use canvas for performance but lose DOM features (accessibility, forms, native controls, CSS styling), or overlay DOM elements on top of the canvas and manage two separate coordinate systems. HTML-in-canvas collapses that into one: nest HTML inside a <canvas>, then draw it directly into the rendering pipeline — with keyboard navigation and screen reader support preserved.

Source: Chrome for Developers — HTML-in-Canvas origin trial blog post; WHATWG HTML PR #11588, May 2026.

three new primitives

PrimitiveWhat it does
layoutsubtree attribute on <canvas> Opts the canvas into HTML-in-canvas mode. Child DOM elements are laid out and made accessible but not visually rendered by the browser — the canvas draws them instead.
ctx.drawElementImage(element, x, y) 2D canvas: draws the element at the given position; returns a DOMMatrix to sync the element's style.transform with its drawn location so DOM events remain aligned.
gl.texElementImage2D(…, element) / device.copyElementImageToTexture(element, …) WebGL / WebGPU: uploads the element as a texture, enabling it to be used in shaders.
canvas.onpaint event Fires whenever the nested HTML needs to be redrawn (DOM mutation, CSS change, user interaction). The handler redraws the element into the canvas.
Source: WICG/html-in-canvas explainer; blink-dev Intent to Experiment, May 2026.

examples

Drawing interactive DOM form elements into a 2D canvas context:

<canvas id="c" style="width:400px;height:300px" layoutsubtree>
  <div id="hud">
    <label for="name">Player name:</label>
    <input id="name" type="text">
  </div>
</canvas>

<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const hud = document.getElementById('hud');

canvas.onpaint = () => {
  ctx.reset();
  // Draw the HUD into the canvas; get back the transform for hit-testing
  const t = ctx.drawElementImage(hud, 10, 10);
  // Sync the DOM position so clicks reach the correct element
  hud.style.transform = t.toString();
};
</script>

WebGL texture upload

// Assumes canvas has layoutsubtree and contains an element with id="ui"
canvas.onpaint = () => {
  const uiElement = document.getElementById('ui');
  gl.bindTexture(gl.TEXTURE_2D, uiTexture);
  gl.texElementImage2D(
    gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE,
    uiElement
  );
  renderScene(); // draw the texture on a quad in 3D space
};

WebGPU texture upload

// Same layoutsubtree canvas; device from navigator.gpu.requestAdapter()/requestDevice().
// Current explainer shape: GPUQueue.drawElementImageToTexture(source, destination).
// The OT-era chromestatus record names this device.copyElementImageToTexture (as in the
// syntax table above); test which form your Chrome build exposes.
canvas.onpaint = () => {
  const uiElement = document.getElementById('ui');
  device.queue.drawElementImageToTexture(
    { source: uiElement },
    { texture: uiTexture, size: { width: 400, height: 300, depthOrArrayLayers: 1 } }
  );
  renderScene();
};
Source: WICG/html-in-canvas explainer; Chrome for Developers blog; blink-dev thread, May 2026.

browser support

BrowserSupport
Chrome148 – 150 (origin trial: HTMLInCanvas)
FirefoxNo signal (standards-positions #1076)
SafariPositive signal (standards-positions #630)
Source: ChromeStatus feature 5172548013916160; mozilla standards-positions #1076; WebKit standards-positions #630.

see also