The Problem: Why Can't We Draw HTML on Canvas?

HTML and Canvas are great at two completely different things:

  • HTML & CSS handle text formatting, buttons, forms, scrolling, keyboard navigation, and screen reader accessibility effortlessly.
  • HTML5 <canvas> handles high-speed 2D rendering, 3D scenes (WebGL/WebGPU), video processing, and games.

When developers need to combine both — such as putting an interactive computer screen inside a 3D game or adding a styled menu to a canvas app — they historically faced two messy workarounds:

Workaround 1CSS Overlay Hack

Floating HTML elements on top of the canvas using position: absolute. This breaks whenever the element needs to be rotated, distorted, or placed inside a 3D world.

Workaround 2Reinventing the Wheel

Writing thousands of lines of custom JavaScript to manually draw text, fake input carets, calculate line wraps, and simulate buttons from scratch inside the canvas.

The Solution: Native HTML-in-Canvas

The HTML in Canvas API is an emerging web platform standard incubated by the WICG (Web Incubator Community Group). It provides a native bridge between real DOM elements and graphics contexts.

Instead of hacking overlays or reinventing text layout, you place regular HTML elements inside the <canvas> tag. The browser’s native rendering engine automatically lays out the HTML and passes it directly to your canvas as a GPU texture.

Why this is a game changer: Because the elements remain real DOM nodes, they stay 100% accessible to screen readers, respond to CSS styles, and support keyboard focus and tab navigation out of the box.

How It Works: The Core Primitives

The proposed API introduces a few simple primitives to connect HTML with Canvas:

1. The layoutsubtree Attribute

Adding layoutsubtree to a <canvas> tells the browser that its child elements should participate in layout and hit testing, but should not be painted in the regular page flow.

2. The paint Event & requestPaint()

Instead of running a heavy animation loop continuously, the canvas fires a paint event only when child HTML visually changes (e.g. on hover, typing, or DOM mutations). You can also request a re-render manually using canvas.requestPaint().

3. Drawing into Graphics Contexts

The specification provides methods tailored for each graphics pipeline:

  • 2D Canvas: ctx.drawElementImage(element, x, y) draws the element and returns a transform matrix to keep click hit-testing aligned.
  • WebGL: gl.texElementImage2D(...) uploads the element directly as a 3D texture.
  • WebGPU: copyElementImageToTexture() copies the element layout bitmap into a GPU texture.

4. captureElementImage() for Web Workers

For multi-threaded apps using OffscreenCanvas, you can capture an ElementImage snapshot on the main thread and transfer it to a Web Worker for background rendering.

Code Example (Proposed Syntax)

Here is how drawing an interactive HTML card onto a 2D canvas looks with the proposed syntax:

Rendering HTML into 2D Canvas
<!-- Step 1: Canvas with 'layoutsubtree' and HTML children --> <canvas id="output-canvas" width="500" height="300" layoutsubtree> <div id="ui-card" style="padding: 20px; background: #0d6efd; color: #fff; border-radius: 8px;"> <h2>Player Inventory</h2> <input type="text" placeholder="Search items..." /> <button id="equip-btn">Equip Item</button> </div> </canvas> <script> const canvas = document.getElementById('output-canvas'); const uiCard = document.getElementById('ui-card'); const ctx = canvas.getContext('2d'); // Step 2: Listen for the 'paint' event canvas.onpaint = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); // Step 3: Draw the element onto the canvas (returns a DOMMatrix) const transform = ctx.drawElementImage(uiCard, 50, 50); // Step 4: Synchronize DOM position with visual canvas position // This ensures mouse clicks, touches, and keyboard focus land in the right spot! uiCard.style.transform = transform.toString(); }; </script>

Privacy-Preserving Painting

Because canvas pixel data can be read back using JavaScript (e.g. getImageData()), the specification incorporates strict privacy-preserving painting rules to prevent data leaks:

  • Cross-Origin Isolation: Iframes, images, and external stylesheets from other domains are blocked from painting to prevent cross-site data theft.
  • User Secrets: Form autofill suggestions, password contents, and spellcheck squiggles are excluded from the paint buffer.
  • Browsing History Protection: Visited link styling (:visited) is treated as unvisited to prevent scripts from probing user history.
  • Anti-Aliasing Hardening: Subpixel text anti-aliasing is disabled to eliminate pixel-timing attacks.

Current Status & How to Test

The HTML in Canvas API is an active WICG incubation proposal. It is currently available for experimentation behind a feature flag in Chromium browsers (such as Chrome Canary).

To test it today in Chrome Canary:

Navigate to chrome://flags/#canvas-draw-element, set the flag to Enabled, and restart your browser.

For official technical specifications, discussions, and updates, visit the WICG html-in-canvas GitHub repository and the ChromeStatus entry.