Back to Blog
ArticleOctober 31, 2026

Chrome 157 HTML-in-Canvas: Real HTML, Live Controls, and Native Canvas Export

Learn Chrome 157 HTML-in-Canvas: drawElementImage examples, PNG export, WebGL and WebGPU integration, API migration, and practical fallback strategies.

Listen to this article

On This Page8 sections

Key Takeaways

  • HTML-in-Canvas brings real HTML into Canvas 2D, WebGL, and WebGPU, with DOM semantics and browser interactions retained in the live application.
  • Chrome 157 is the announced shipping milestone. As of October 30, 2026, its rollout is still underway; the scheduled stable release is November 3.
  • The current API uses content="drawable", drawable, and drawElementImage(). Earlier experimental examples require migration.
  • Start with a contained feature, such as an editable card or a game panel. Keep a visible HTML fallback and validate exports, keyboard access, and performance.

Google's October 26 announcement describes a substantial change for canvas applications: HTML can become part of their rendered output while remaining structured, interactive content. That makes the feature particularly relevant to design editors and interfaces embedded in 3D scenes. genui{"citation":{"ref":"turn0view0"}}

Is HTML-in-Canvas Available in Chrome Yet?

Availability checked: October 30, 2026. The feature announcement and the browser rollout have different dates.

MilestoneDateMeaning
Chrome announces HTML-in-Canvas for version 157October 26, 2026Identifies the shipping version
Desktop early stable updateOctober 28, 2026Chrome 157 reaches a small percentage of Windows users; the notice says Mac follows
Scheduled stable releaseNovember 3, 2026Published release target, not proof that every installation has updated

The early stable notice and Chrome 157 release schedule explain why an announcement does not mean immediate availability for every visitor. genui{"citation":{"refs":["turn1search1","turn0view2"]}}

Use runtime detection on every target browser. Do not infer support in Edge, Safari, Firefox, embedded browsers, or mobile apps from a Chrome announcement. A usable fallback should remain part of the product until its actual browser matrix passes testing.

What HTML-in-Canvas Actually Does

The browser lays out eligible HTML, produces drawable snapshots, and lets the application composite them into a canvas. The original elements remain DOM nodes.

This distinction matters: the live interface retains semantics; the exported PNG contains pixels. A button inside the application can remain interactive, but a downloaded image does not contain a working button.

Chrome documents support for text selection, browser search, translation, accessibility integration, and image or video export. These capabilities make the feature more useful than an ordinary screenshot pipeline. genui{"citation":{"ref":"turn1view0"}}

An architectural implication is that an editor can use CSS for card typography while its graphics layer handles placement and effects. Application responsibilities such as undo, saved documents, permissions, and asset management still need their own implementation.

HTML-in-Canvas vs. html2canvas and HTML Overlays

ApproachBest fitMain consideration
Canvas drawing commandsCustom graphics and visualizationsText editing and semantic controls need additional design
HTML positioned over canvasConventional controls around a graphics sceneCoordinate alignment and visual stacking need coordination
html2canvasClient-side snapshots of existing DOMReconstructs supported DOM/CSS properties rather than taking a browser screenshot
HTML-in-CanvasInteractive HTML integrated into canvas compositionRequires supported APIs, eligible descendants, and a redraw strategy

The html2canvas documentation explains that its output is reconstructed from DOM information and may differ from the browser's rendering. Native drawing can remove that particular reconstruction layer, but it does not eliminate compatibility or export validation. genui{"citation":{"ref":"turn2view4"}}

Do not treat drawElementImage(document.body) as a general screenshot API. The proposal requires an eligible drawable element under the appropriate canvas, with an available rendering snapshot. Keep whole-page capture workflows separate from an editor designed around this API. genui{"citation":{"ref":"turn4view2"}}

The Current API: Attributes, Drawing, and Geometry

APIPurpose
content="drawable"Enables layout of the canvas's descendant content
drawableMarks an independently drawable element
drawElementImage()Draws that element into a 2D context
paintSignals a rendering opportunity for updated content
requestPaint()Requests a paint event
updateElementGeometry()Supplies interaction geometry, particularly for 3D rendering

These responsibilities are separate: markup identifies content, drawing produces pixels, and geometry aligns interaction with those pixels. In 2D, drawing normally updates geometry automatically. The 2D guide also documents an advanced opt-out through preserveElementGeometry. genui{"citation":{"ref":"turn2view1"}}

A Minimal Example With a Visible HTML Fallback

This fixed-size example targets the documented Chrome 157 API. The card starts outside the canvas, so unsupported browsers retain a visible button. Supported browsers move the same node into the drawable subtree.

html
<style>
  #stage { width: 640px; height: 240px; }
  #card {
    box-sizing: border-box;
    width: 560px;
    padding: 24px;
    border-radius: 20px;
    background: #eef2ff;
    color: #172554;
    font: 18px system-ui;
  }
</style>

<p id="status" role="status">Standard HTML mode.</p>
<div id="fallback">
  <article id="card" drawable>
    <h2>A real HTML card</h2>
    <p>Select this text or press the button.</p>
    <button id="counter" type="button">Clicks: 0</button>
  </article>
</div>
<canvas id="stage" width="640" height="240"
        content="drawable" hidden></canvas>

<script>
  const stage = document.getElementById('stage');
  const card = document.getElementById('card');
  const fallback = document.getElementById('fallback');
  const status = document.getElementById('status');
  const counter = document.getElementById('counter');
  const ctx = stage.getContext('2d');
  let clicks = 0;

  counter.addEventListener('click', () => {
    counter.textContent = 'Clicks: ' + (++clicks);
  });

  const supported =
    stage.content === 'drawable' &&
    typeof stage.requestPaint === 'function' &&
    ctx && typeof ctx.drawElementImage === 'function';

  if (supported) {
    stage.append(card);
    stage.hidden = false;

    function paint() {
      try {
        ctx.reset();
        ctx.drawElementImage(card, 40, 20);
      } catch (error) {
        stage.removeEventListener('paint', paint);
        fallback.append(card);
        stage.hidden = true;
        status.textContent = 'Standard HTML mode.';
        console.error(error);
      }
    }

    stage.addEventListener('paint', paint);
    status.textContent = 'Native canvas mode.';
    stage.requestPaint();
  }
</script>

The fallback is deliberately outside the canvas. Traditional canvas fallback children are not a reliable visible alternative in a browser that supports canvas but lacks this new feature. The distinction follows the existing canvas fallback model. genui{"citation":{"ref":"turn5view4"}}

Before production use, add responsive sizing, high-density rendering, and an explicit export state. Test both branches rather than assuming that successful feature detection proves every interaction works.

Paint Timing and Nested Drawable Elements

Two details in the technical concepts guide prevent common rendering errors:

  • Nested drawable elements are independent. Marking a button drawable inside a drawable card excludes it from the card's snapshot; draw it separately.
  • DOM writes inside paint appear in a later rendering update. A handler that continually changes visual styles can continually schedule more painting.

changedElements identifies drawable elements whose appearance changed. It is an invalidation signal, not a complete dirty-rectangle algorithm. genui{"citation":{"ref":"turn3view0"}}

For a small editor, redraw the complete composition first. If optimizing later, account for overlapping objects and exposed background areas. Clearing the entire canvas and redrawing only changed elements will erase unchanged content.

High-DPI Rendering: Keep Coordinate Systems Explicit

CSS dimensions, canvas grid dimensions, and texture dimensions are different quantities. Chrome specifies source cropping rectangles in CSS pixels, 2D destination coordinates in canvas grid pixels, and geometry transforms in CSS pixels. genui{"citation":{"ref":"turn3view1"}}

For a proposed 640 × 360 CSS-pixel preview rendered into a 1280 × 720 backing grid, the conversion ratio is 2 on each axis. A 20 CSS-pixel inset corresponds to 40 grid pixels.

Store these ratios explicitly. Test browser zoom, window resizing, and moving the window between displays. Avoid applying a context scale and independently multiplying every coordinate unless that double transformation is intentional.

Export resolution deserves a separate setting: a social image requested at 1200 × 630 should not change dimensions merely because the editor runs on a different monitor.

Exporting HTML-in-Canvas to PNG

Once the composition has been painted, use the standard canvas export mechanism:

javascript
function canvasToPng(canvas) {
  return new Promise((resolve, reject) => {
    try {
      canvas.toBlob((blob) => {
        if (blob) resolve(blob);
        else reject(new Error('PNG encoding failed.'));
      }, 'image/png');
    } catch (error) {
      reject(error);
    }
  });
}

The toBlob reference specifies the asynchronous callback and the possibility of a null result. An origin-clean canvas is required for export. genui{"citation":{"ref":"turn5view0"}}

Use this export sequence:

  1. Commit the intended application state.
  2. Wait for required fonts and image assets.
  3. Request a paint and wait until the drawing handler finishes the intended composition.
  4. Encode the canvas and offer the resulting file.

document.fonts.ready can coordinate used fonts, but it does not wait for image assets or complete the canvas draw itself. The Font Loading API documentation explains that distinction. genui{"citation":{"ref":"turn5view1"}}

For reproducible exports, pause decorative animation, define a stable editor state, and provide a visible error if an asset fails. In a transparent output workflow, inspect the actual alpha channel rather than relying on a checkerboard preview.

WebGL and WebGPU: HTML as a Texture

WebGL: allocate texture storage before calling texElementSubImage2D(). The current method updates existing storage; allocate again when the required dimensions change. Uploading the element and drawing a mesh are separate steps. Chrome's WebGL guide documents this lifecycle. genui{"citation":{"ref":"turn2view2"}}

WebGPU: allocate a texture with appropriate usage flags, then upload through the queue:

javascript
// Run during paint, after allocating htmlTexture.
device.queue.drawElementImageToTexture(
  { source: panelElement },
  { texture: htmlTexture }
);

The WebGPU guide lists TEXTURE_BINDING, COPY_DST, and RENDER_ATTACHMENT for its texture example. WebGPU availability alone does not establish support for this additional queue method. genui{"citation":{"ref":"turn4view3"}}

Both 3D paths require geometry synchronization. A texture on a visible mesh does not tell the browser where its controls should receive input. Update geometry as the camera or object moves.

Engineering implication: arbitrary shader distortion needs special care. A single matrix does not describe every nonlinear deformation. Validate clicking, selection, and focus against the displayed surface rather than assuming visual success proves interaction accuracy.

Migrating Older HTML-in-Canvas Examples

Chrome's September 8 migration notice documents substantial API changes. Use the current technical guides alongside it.

Older patternCurrent implementation
<canvas layoutsubtree><canvas content="drawable">
Unmarked drawing targetsAdd drawable to independent drawing roots
texElementImage2D()texElementSubImage2D(), with preallocated storage and revised arguments
copyElementImageToTexture()drawElementImageToTexture(), with revised dictionaries
Using the return value of drawElementImage() as a matrixThe method returns undefined; 2D geometry normally updates automatically

Renaming methods alone is insufficient. Check argument shapes, texture allocation, and geometry handling together. genui{"citation":{"refs":["turn2view5","turn2view0"]}}

Framework Support and React Integration

The Chrome ecosystem overview lists these integrations:

LibraryDocumented integration
Three.jsExperimental THREE.HTMLTexture
Babylon.jsBABYLON.HtmlTexture
PixiJSHTMLSource, listed from v8.19.0
PlayCanvasHTML texture updates through setSource()

These are third-party projects. Pin a compatible release and inspect its examples; a framework wrapper does not supply missing browser support. genui{"citation":{"ref":"turn0view1"}}

For React, the integration target is the component's DOM output. A practical architecture is to let React own markup and application state while a small adapter owns canvas events and rendering.

  • Attach drawing listeners after the canvas and elements mount.
  • Clean up listeners, observers, and animation callbacks when the adapter unmounts.
  • Keep render callbacks independent of unnecessary React state updates.
  • Preserve user input when switching between native and fallback presentations.

This follows React's effect setup and cleanup model, including the extra development setup/cleanup cycle in Strict Mode. genui{"citation":{"ref":"turn5view2"}}

Check component portals carefully. React's portal documentation confirms that a portal changes physical DOM placement. A menu portaled into document.body is therefore outside the canvas subtree and needs an explicit presentation strategy. genui{"citation":{"ref":"turn5view3"}}

Security and Accessibility Boundaries

The WICG security review requires sensitive information such as protected cross-origin content, visited-link state, and autofill previews to be excluded from readable output. Rendering HTML into canvas does not authorize reading another origin's private content. genui{"citation":{"ref":"turn3view4"}}

Do not advertise unrestricted webpage capture or assume every embedded resource will appear in an export. Build an asset fixture with same-origin images, authorized cross-origin images, and third-party embeds, then document the observed output.

For accessibility, preserve real labels and logical keyboard order. Include keyboard-only use, focus visibility, zoom, text selection, and a screen-reader pass in acceptance testing. Structured HTML provides useful foundations; the complete experience still depends on application design.

Performance: Measure the Workload That Matters

A native API is not evidence of a fixed speedup. The useful product question is whether it reduces total work for the intended interface.

Use the following proposed comparison plan; these are measurements to collect, not published benchmark results:

ScenarioMeasureDecision it informs
Editing a single rich-text cardInput-to-visible-update latency and text fidelityWhether the editing experience improves
Updating a dashboard of cardsMain-thread time and number of repaintsWhether redraw granularity is appropriate
Moving a camera past HTML panelsFrame-time distribution and pointer accuracyWhether 3D integration is ready
Exporting a large compositionEncoding time, memory, and pixel outputWhether export remains reliable

Compare identical content, fonts, dimensions, device settings, and cache conditions. Separate asset-loading time from rendering time. Check lower-powered hardware before using a smooth desktop demonstration as a launch criterion.

A useful optimization hypothesis is to update static HTML textures only when their content changes while continuing to render a moving scene. Verify the benefit with profiling.

Demos Worth Studying

The GoogleChromeLabs collection links to community examples and their source code.

DemoFocus for evaluation
Chrome demonstration collectionStarting points for HTML integrated into graphics
Curved MarkupSelectable text on a curved screen
HTML in Canvas DemoShader filters, page curl, and CRT effects
Liquid DOMLive content combined with WebGPU glass effects

Community examples are demonstrations, not cross-browser certification. Check their browser requirements and API revision before reusing code. genui{"citation":{"ref":"turn3view5"}}

Common Problems and First Checks

SymptomFirst check
drawElementImage is missingRuntime support and actual browser build
Drawing throws on startupCanvas ancestry, drawable, and availability of the initial snapshot
A nested control disappearsWhether it became an independent drawable root
The fallback is invisibleWhether it remains inside a normally supported canvas
An export shows older textWhether export ran before the requested composition was drawn
A 3D button looks correct but misses clicksGeometry synchronization and nonlinear distortion
A texture upload failsStorage allocation, dimensions, arguments, and API revision

Start with one card and one control, then add resizing, export, and scene transforms separately. This isolates failures more effectively than debugging every integration layer at once.

Conclusion

HTML-in-Canvas creates a useful connection between browser-native HTML and canvas composition. Its strongest adoption case is a feature that needs both rich DOM behavior and custom graphics.

Build one measurable prototype: an editable card, a reliable export, and a visible fallback. Validate it across the intended browsers and input methods, then expand only when fidelity, interaction, and performance justify the change.

Share this article

Referenced Tools

Browse entries that are adjacent to the topics covered in this article.

Explore directory