![Natalia Markoborodova](https://web.dev/images/authors/nmarkoborodova.jpg) Natalia Markoborodova [X](https://twitter.com/nmarkoborodova) [GitHub](https://github.com/ewewraw) [LinkedIn](https://www.linkedin.com/in/natalia-markoborodova) [Bluesky](https://bsky.app/profile/nmarkoborodova.bsky.social)

<br />

Rendering HTML inside a WebGPU canvas offers all the benefits that HTML in a WebGL canvas does, such as applying shader effects to the DOM or integrating interactive HTML into 3D scenes. However, [WebGPU offers significant benefits compared to WebGL](https://developer.chrome.com/docs/web-platform/webgpu/overview.md.txt).

This guide demonstrates how to render HTML into a WebGPU canvas. Learn more about [HTML-in-Canvas in the API overview](https://developer.chrome.com/docs/web-platform/html-in-canvas.md.txt).

Rendering HTML in a WebGPU canvas requires the following steps:

1. Prepare your canvas. Declaratively set the right attributes to let the browser know it should render HTML into a canvas. This setup is identical across 2D, WebGL, and WebGPU contexts. Learn how to [set your canvas up](https://developer.chrome.com/docs/web-platform/html-in-canvas/concepts.md.txt#prepare) and how to [declare HTML elements to be drawn independently](https://developer.chrome.com/docs/web-platform/html-in-canvas/concepts.md.txt#drawable).
2. Apply the HTML as a texture:
   1. Pre-allocate GPU texture storage.
   2. Upload the DOM snapshot as a texture.
3. Synchronize the DOM and the pixels.

## Draw an HTML element as a WebGPU texture

In WebGPU, HTML elements are uploaded to the GPU as 2D textures using `drawElementImageToTexture()`.

### Pre-allocate texture storage

WebGPU requires explicit memory allocation. Before copying DOM pixels into a texture, you must reserve GPU VRAM using the [`device.createTexture()` method](https://developer.mozilla.org/docs/Web/API/GPUDevice/createTexture):

```javascript
const context = canvas.getContext("webgpu");

// Pre-allocate the texture size as needed
// For example: HTML element CSS size × devicePixelRatio
const size = canvas.getElementImageDefaultSize(myElement);
const myElementWidth = Math.ceil(size.width);
const myElementHeight = Math.ceil(size.height);

// Pre-allocate the GPUTexture backing (run once during setup)
const myTexture = device.createTexture({
  size: {
    width: myElementWidth,
    height: myElementHeight,
    depthOrArrayLayers: 1, // optional (defaults to 1)
  },
  format: "rgba8unorm",
  usage:
    GPUTextureUsage.TEXTURE_BINDING |
    GPUTextureUsage.COPY_DST |
    GPUTextureUsage.RENDER_ATTACHMENT,
});
```

### Draw HTML element image

In WebGPU, the `drawElementImageToTexture()` method draws the snapshot of the HTML element. The method takes two dictionary objects: `source` and `destination`.

`source` defines the source DOM element to rasterize and optional sub-rectangle clipping coordinates that let you copy only a portion of the element into your texture rather than the entire DOM node:

| Property | Type | Description |
|---|---|---|
| `source` required | `Element` or `ElementImage` | The target DOM element (or pre-captured `ElementImage` instance) to draw into the texture. |
| `sourceX` optional (all four or none) | float | The horizontal X-coordinate offset (in CSS pixels) defining the top-left corner of the source sub-rectangle slice. |
| `sourceY` optional (all four or none) | float | The vertical Y-coordinate offset (in CSS pixels) defining the top-left corner of the source sub-rectangle slice. |
| `sourceWidth` optional (all four or none) | float | The width (in CSS pixels) of the source sub-rectangle to crop and rasterize. |
| `sourceHeight` optional (all four or none) | float | The height (in CSS pixels) of the source sub-rectangle to crop and rasterize. |

`destination` defines the target GPU texture that receives the rendered DOM snapshot, along with optional destination offsets, mip levels, and copy dimensions to place the pixels into a specific region of the texture:

| Property | Type | Description |
|---|---|---|
| `texture` required | `GPUTexture` | The pre-allocated destination WebGPU texture that will receive the rendered DOM pixel snapshot. |
| `size` optional | `GPUExtent3D` | The width and height (in physical texture pixels, for example, `{ width, height }`) to scale and rasterize the element within the destination texture. |
| `origin` optional | `GPUOrigin3D` | The 3D destination offset (x, y, z) within the texture where the rasterized pixels will be written. Defaults to `[0, 0, 0]`. |
| `mipLevel` optional | `GPUIntegerCoordinate` | The specific mipmap level of the destination texture to write into. Defaults to `0`. |
| `aspect` optional | `GPUTextureAspect` | Specifies which texture aspect to copy into (`"all"`, `"stencil-only"`, or `"depth-only"`). Defaults to `"all"`. |
| `colorSpace` optional | `PredefinedColorSpace` | The destination color space (for example, `"srgb"`, `"display-p3"`) for color conversion upon upload. Defaults to `"srgb"`. |
| `premultipliedAlpha` optional | boolean | Whether alpha should be premultiplied into the color channels during upload. Defaults to `false`. |

Upload the element into your texture inside the `paint` event by calling the `drawElementImageToTexture()` method:

```javascript
canvas.addEventListener("paint", (event) => {
  const sourceDict = {
    source: myElement,
    // Optional sub-rectangle crop in CSS pixels
    // (defaults to full element if omitted):
    sourceX: 0,
    sourceY: 0,
    sourceWidth: myElement.offsetWidth,
    sourceHeight: myElement.offsetHeight,
  };

  const destDict = {
    texture: myTexture,
    size: { width: myElementWidth, height: myElementHeight },
  };

  // Upload the latest DOM snapshot into the pre-allocated GPUTexture
  device.queue.drawElementImageToTexture(sourceDict, destDict);
});
```

## Synchronize geometry

To align DOM hit-testing and accessibility focus rings with the rendered canvas output, construct a `DOMMatrix` transform and apply it with the `updateElementGeometry()` method:

```javascript
// Construct a transform as needed in CSS pixels
// relative to the canvas, for example:
// 1/8 of the canvas width from the left edge, vertically centered.
// scaleX, scaleY: canvas grid pixels per CSS pixel (from resize observer)
const canvasCssWidth = canvas.width / scaleX;
const canvasCssHeight = canvas.height / scaleY;
const elementCssHeight = canvas.getElementImageDefaultSize(myElement).height / scaleY;
const x = canvasCssWidth / 8;
const y = (canvasCssHeight - elementCssHeight) / 2;
const transform = new DOMMatrix().translate(x, y);

// Synchronize the element's DOM hit-testing and accessibility geometry
canvas.updateElementGeometry(myElement, { canvasTransform: transform });
```

## Next steps

- Understand the [core technical concepts behind HTML-in-Canvas](https://developer.chrome.com/docs/web-platform/html-in-canvas/concepts.md.txt).
- Learn how to [render HTML in a 2D context](https://developer.chrome.com/docs/web-platform/html-in-canvas/2d.md.txt).
- Learn how to [render HTML in a WebGL Canvas](https://developer.chrome.com/docs/web-platform/html-in-canvas/webgl.md.txt).
- Learn about [fallback strategies](https://developer.chrome.com/docs/web-platform/html-in-canvas/concepts.md.txt#fallback).
- File an [issue on GitHub](https://github.com/WICG/html-in-canvas/issues) to share your feedback.
- Check out the [HTML-in-Canvas demo collection by the community](https://github.com/GoogleChromeLabs/css-web-ui-demos/blob/main/html-in-canvas/awesome-html-in-canvas.md).