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, anddrawElementImage(). 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.
| Milestone | Date | Meaning |
|---|---|---|
| Chrome announces HTML-in-Canvas for version 157 | October 26, 2026 | Identifies the shipping version |
| Desktop early stable update | October 28, 2026 | Chrome 157 reaches a small percentage of Windows users; the notice says Mac follows |
| Scheduled stable release | November 3, 2026 | Published 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
| Approach | Best fit | Main consideration |
|---|---|---|
| Canvas drawing commands | Custom graphics and visualizations | Text editing and semantic controls need additional design |
| HTML positioned over canvas | Conventional controls around a graphics scene | Coordinate alignment and visual stacking need coordination |
| html2canvas | Client-side snapshots of existing DOM | Reconstructs supported DOM/CSS properties rather than taking a browser screenshot |
| HTML-in-Canvas | Interactive HTML integrated into canvas composition | Requires 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
| API | Purpose |
|---|---|
content="drawable" | Enables layout of the canvas's descendant content |
drawable | Marks an independently drawable element |
drawElementImage() | Draws that element into a 2D context |
paint | Signals 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.
<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
drawableinside a drawable card excludes it from the card's snapshot; draw it separately. - DOM writes inside
paintappear 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:
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:
- Commit the intended application state.
- Wait for required fonts and image assets.
- Request a paint and wait until the drawing handler finishes the intended composition.
- 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:
// 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 pattern | Current implementation |
|---|---|
<canvas layoutsubtree> | <canvas content="drawable"> |
| Unmarked drawing targets | Add 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 matrix | The 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:
| Library | Documented integration |
|---|---|
| Three.js | Experimental THREE.HTMLTexture |
| Babylon.js | BABYLON.HtmlTexture |
| PixiJS | HTMLSource, listed from v8.19.0 |
| PlayCanvas | HTML 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:
| Scenario | Measure | Decision it informs |
|---|---|---|
| Editing a single rich-text card | Input-to-visible-update latency and text fidelity | Whether the editing experience improves |
| Updating a dashboard of cards | Main-thread time and number of repaints | Whether redraw granularity is appropriate |
| Moving a camera past HTML panels | Frame-time distribution and pointer accuracy | Whether 3D integration is ready |
| Exporting a large composition | Encoding time, memory, and pixel output | Whether 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.
| Demo | Focus for evaluation |
|---|---|
| Chrome demonstration collection | Starting points for HTML integrated into graphics |
| Curved Markup | Selectable text on a curved screen |
| HTML in Canvas Demo | Shader filters, page curl, and CRT effects |
| Liquid DOM | Live 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
| Symptom | First check |
|---|---|
drawElementImage is missing | Runtime support and actual browser build |
| Drawing throws on startup | Canvas ancestry, drawable, and availability of the initial snapshot |
| A nested control disappears | Whether it became an independent drawable root |
| The fallback is invisible | Whether it remains inside a normally supported canvas |
| An export shows older text | Whether export ran before the requested composition was drawn |
| A 3D button looks correct but misses clicks | Geometry synchronization and nonlinear distortion |
| A texture upload fails | Storage 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.
Continue Reading
More articles connected to the same themes, protocols, and tools.
Referenced Tools
Browse entries that are adjacent to the topics covered in this article.







