How CSS Paint API Works with paint()

The CSS Paint API, part of the CSS Houdini suite of standards, allows developers to generate custom, dynamic graphics programmatically directly within CSS using JavaScript. By invoking the paint(workletName) function inside standard CSS properties—such as background-image or border-image—the browser executes a designated Paint Worklet that renders visuals onto a 2D canvas context. This article explains the technical mechanics behind registering a worklet, invoking it via the paint() function, passing dynamic CSS variables, and how the browser handles the rendering lifecycle.

What is a Paint Worklet?

A Paint Worklet is a lightweight JavaScript file that runs off the main thread inside the browser’s rendering pipeline. It functions similarly to the HTML5 Canvas API, providing a 2D drawing context (PaintRenderingContext2D) to draw shapes, lines, gradients, and colors directly into an element’s visual area before it is painted to the screen.

Step 1: Defining the Paint Worklet (JavaScript)

To use paint(workletName), you must first define a class and register it using the global registerPaint() function inside a dedicated JavaScript file.

// my-worklet.js
class CustomBackgroundPainter {
  // Define which CSS custom properties the painter observes
  static get inputProperties() {
    return ['--custom-color', '--stripe-width'];
  }

  // The paint method is called whenever the element needs to be repainted
  paint(ctx, geometry, properties) {
    const color = properties.get('--custom-color').toString().trim() || 'blue';
    const width = parseFloat(properties.get('--stripe-width')) || 20;

    ctx.fillStyle = color;
    ctx.fillRect(0, 0, geometry.width, width);
  }
}

// Register the class under a unique worklet name
registerPaint('customBackground', CustomBackgroundPainter);

The paint() method automatically receives three parameters:

  1. ctx: A 2D rendering context providing standard drawing methods like fillRect(), arc(), and beginPath().
  2. geometry: An object containing the width and height of the area being drawn onto.
  3. properties: A style map used to read the CSS properties defined in inputProperties.

Step 2: Registering the Worklet in HTML

The JavaScript file containing the worklet must be loaded by the main document using the CSS.paintWorklet.addModule() method:

<script>
  if ('paintWorklet' in CSS) {
    CSS.paintWorklet.addModule('my-worklet.js');
  }
</script>

This loads the worklet script into the browser's paint pipeline.

Step 3: Invoking the Worklet with paint() in CSS

Once the worklet is registered under the identifier customBackground, you reference it inside your CSS rules using the paint() functional notation anywhere an image is accepted.

.box {
  --custom-color: #ff5722;
  --stripe-width: 40px;
  
  width: 300px;
  height: 200px;
  background-image: paint(customBackground);
}

When the browser parses background-image: paint(customBackground), it looks for a registered worklet named customBackground, runs its paint() function, and uses the resulting graphics as the background.

Dynamic Repainting via CSS Properties

The Paint API integrates tightly with the CSS Object Model (CSSOM). If an observed CSS custom property (listed in inputProperties) changes—either through user interaction, animations, or media queries—the browser automatically triggers the paint() function again:

.box:hover {
  --custom-color: #4caf50;
  --stripe-width: 60px;
}

When the element is hovered, the browser invalidates the paint cache, reads the new values, and re-renders the canvas immediately without requiring manual DOM manipulation or custom requestAnimationFrame loops.

Passing Direct Arguments to paint()

The paint() function also supports passing inline arguments, defined via the inputArguments static getter within the worklet class.

In the worklet:

static get inputArguments() {
  return ['<color>', '<length>'];
}

paint(ctx, geometry, properties, args) {
  const color = args[0].toString();
  const radius = parseFloat(args[1]);
  // Drawing logic using args...
}

In the CSS:

.box {
  background-image: paint(customBackground, purple, 15px);
}

Performance Benefits

Because Paint Worklets operate within the browser's native rendering engine:

  • They do not add nodes to the DOM tree, keeping memory footprints low.
  • Repainting occurs during the paint phase of the rendering pipeline, avoiding unnecessary layout recalculations.
  • Rendering can often be optimized or threaded by the browser engine, offering superior performance compared to using standard HTML Canvas overlays or complex inline SVGs.