Interactive demos and documentation for the WICG HTML-in-Canvas specification — render real, styled, accessible DOM elements directly into a
<canvas>viadrawElementImage().
html-in-canvas.dev → · Demo gallery · Spec docs · Contributing
A community resource maintained by En Dash Consulting that shows what becomes possible when the browser can paint live HTML into a canvas: real text editing, native i18n, accessible chart labels, video recording from HTML scenes, GPU shader effects on DOM content, and 3D scenes that use HTML elements as textures.
The relevant Chromium feature ships behind
chrome://flags/#canvas-draw-element in Chrome Canary, and is also
available in recent Brave Stable builds (≥ 1.89.132 / Chromium 147)
at brave://flags/#canvas-draw-element. Firefox and Safari have not
announced implementations yet.
Canvas is great for pixels. The DOM is great for everything else — text shaping, accessibility, internationalization, form controls, contenteditable, focus management, hit testing, screen readers. Until now, putting the two together meant either:
- Hand-rolling text layout into
ctx.fillText()(no bidi, no shaping, no writing modes, no ruby, broken emoji) - Shipping a 130 KB
html2canvasclone that approximates CSS rendering in JavaScript (slow, incomplete, never quite right)
The WICG HTML-in-Canvas proposal lets the browser do what it's already great at — render HTML and CSS — and gives canvas authors zero-cost access to those pixels. Three primitives:
<canvas layoutsubtree>
<div id="content">Real, accessible HTML content</div>
</canvas>const ctx = canvas.getContext('2d');
canvas.onpaint = () => {
const transform = ctx.drawElementImage(content, 0, 0);
if (transform) content.style.transform = transform.toString();
};That's the whole API surface. Every demo on the site is built from those primitives plus the existing canvas (2D / WebGL / WebGPU) APIs.
git clone https://github.com/en-dash-consulting/html-in-canvas-dot-dev.git
cd html-in-canvas-dot-dev
npm install
npm run devVisit http://localhost:4321 in Chrome Canary or Brave Stable
(Chromium 147+) with the canvas-draw-element flag enabled. Setup
instructions:
/docs/browser-support/.
| script | what it does |
|---|---|
npm run dev |
Run the Astro dev server with hot reload |
npm run build |
Build the static site to dist/ |
npm run preview |
Preview the built site |
npm run check |
Run astro check (type-check Astro + TS) |
npm test |
Run the Playwright suite against Chrome Canary |
npm run test:ui |
Run Playwright with the interactive UI |
- Astro 6 — static site generation, content collections, file-based routing
- TypeScript for the site infrastructure (vanilla JS in the demos themselves so they're easy to fork)
- Playwright for end-to-end tests against Chrome Canary
- Zero runtime framework — every demo is plain HTML/CSS/JS in a single file, mounted into the wrapped page via shadow DOM
Each demo lives in its own folder under src/content/demos/{slug}/:
src/content/demos/hello-world/
├── meta.json # title, description, tags, browser support
└── demo.html # the demo itself (single self-contained file)
Drop a new folder in there with the same shape and it appears in the gallery automatically. The contributing page has a step-by-step guide and there's a scaffold script:
./scripts/new-demo.sh my-cool-demoEach demo.html is authored to work in two contexts:
- As a standalone HTML page at
/demos/{slug}/demo.html. Anyone can visit it directly, fork it from the GitHub repo, or open it in their own editor. This is what makes the site useful as a learning resource — you can grab any demo and run it locally with zero build tooling. - Mounted via shadow DOM on the wrapped
/demos/{slug}/page, alongside metadata, the source viewer, and page chrome.
The dual-context authoring is glued together by two small conventions:
- Use
body, :host { ... }instead of justbody { ... }for root styling. The:hostselector matches the shadow host in mounted context;bodymatches in standalone. - Use
const root = window.__demoRoot ?? documentat the top of every script, thenroot.getElementById(...)for queries. The wrapped page hands the script its shadow root via a transient global.
These conventions are documented in the _template/demo.html
contributor template — just copy it.
/— landing page/demos/— filterable gallery/demos/{slug}/— wrapped demo page with metadata + source viewer/demos/{slug}/demo.html— standalone demo (open in new tab, fork on GitHub, etc.)/docs/— spec docs synced from the WICG proposal/contributing/— how to add a demo
Every demo has a Playwright smoke test:
import { runDemoSmokeTests } from './helpers/demo-smoke';
runDemoSmokeTests({ slug: 'hello-world' });The shared helper tests both contexts:
- Standalone (
/demos/{slug}/demo.html) — page loads, canvas paints non-blank pixels, no console errors. - Wrapped (
/demos/{slug}/) —#demo-stagehas an open shadow root, the canvas inside it paints, no console errors.
The full suite runs in a few seconds against Chrome Canary. See
tests/helpers/demo-smoke.ts for the runner and playwright.config.ts
for the Chrome Canary launch config (the bundled Chromium that ships
with Playwright doesn't expose the spec, so we point at Canary
directly).
We welcome PRs that add demos, fix bugs, improve the docs, or refine the site itself. The full contributor guide is at /contributing/, but the short version is:
- Fork the repo
- Run
./scripts/new-demo.sh my-demo-name(or copysrc/content/demos/_template/) - Edit
meta.jsonanddemo.html - Run
npm testto make sure your demo paints in both standalone and wrapped contexts - Open a PR
Some good first contributions:
- A demo of a spec primitive that isn't currently shown
- A real-world use case for HTML-in-Canvas you've hit at work
- A bug fix or visual polish on an existing demo
- Tests for an existing demo's interactive behavior beyond the smoke
test (the smoke helper has
setup/interacthooks)
MIT — see LICENSE.
The WICG HTML-in-Canvas spec itself is licensed separately by the Web Incubator Community Group. This project is an unaffiliated community resource and is not endorsed by the spec authors or by Chromium.
- The WICG HTML-in-Canvas authors and contributors who proposed and shipped the spec — especially Philip Rogers, Stephen Chenney (Igalia), Chris Harrelson, Philip Jagenstedt, Khushal Sagar, Vladimir Levin, and Fernando Serboncini
- The Chromium team for the dev-trial implementation behind
canvas-draw-element - Built with care by En Dash Consulting. We build tools that make work better and feel better.