How Does the CSS Paint API Work?

The CSS Paint API, a foundational component of the CSS Houdini suite, allows developers to write custom 2D rendering routines using JavaScript that execute directly inside the browser's native rendering pipeline. Instead of relying on static raster images, heavy SVGs, or extra DOM nodes for complex visual effects, the API exposes a low-level Canvas-like drawing context within a dedicated Paint Worklet. This article explores how the CSS Paint API functions under the hood, how custom paint worklets interact with CSS custom properties, and why this architecture enables high-performance, dynamic visual styling.

Understanding the CSS Houdini Ecosystem

CSS Houdini is a set of low-level APIs designed to give developers direct access to the browser's CSS Object Model (CSSOM) and rendering engine. Traditionally, browser rendering follows a strict pipeline: parsing HTML/CSS, computing styles, calculating layout, painting pixels, and compositing layers. Developers historically had to wait for browser vendors to standardize and implement new CSS properties to alter the paint phase.

The CSS Paint API opens up the paint phase of this pipeline. By exposing a specialized JavaScript interface, it allows developers to programmatically generate an image anywhere a standard CSS <image> is accepted, such as background-image, border-image, mask-image, or list-style-image.

The Architecture of a Paint Worklet

At the center of custom rendering routines is the Paint Worklet. Worklets are lightweight execution threads that run off the main JavaScript thread, separate from the primary window scope. Because painting happens frequently during scrolling, resizing, and animating, running paint logic inside worklets prevents intensive rendering scripts from blocking user interactions or degrading frame rates.

A typical Paint Worklet contains three essential components:

  1. Input Properties Definition: A static getter (inputProperties) that declares which CSS custom properties (variables) or standard CSS properties the worklet needs to track.
  2. Input Arguments (Optional): A static getter (inputArguments) defining function-style arguments passed inside CSS.
  3. The paint() Method: The execution callback invoked by the browser whenever the element requires repainting.

The paint() Callback and Context

When the browser paints an element using a custom routine, it executes the paint(ctx, geometry, properties) function. This method provides direct access to three core parameters:

Connecting JavaScript Routines to CSS Styles

Integrating a custom paint routine into a stylesheet involves two primary steps: registering the worklet module and invoking it within standard CSS rules.

1. Registering the Worklet Module

The JavaScript file defining the class is registered inside the worklet global scope using registerPaint:

class RippleBackground {
  static get inputProperties() {
    return ['--ripple-color', '--ripple-radius'];
  }

  paint(ctx, geometry, properties) {
    const color = properties.get('--ripple-color').toString().trim() || '#3498db';
    const radius = parseFloat(properties.get('--ripple-radius')) || 20;

    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.arc(geometry.width / 2, geometry.height / 2, radius, 0, 2 * Math.PI);
    ctx.fill();
  }
}

registerPaint('ripple-background', RippleBackground);

2. Loading and Applying in CSS

On the main thread, the worklet script is loaded via CSS.paintWorklet.addModule(). In CSS, the element applies the routine using the paint() functional notation:

.interactive-card {
  --ripple-color: #e74c3c;
  --ripple-radius: 50;
  background-image: paint(ripple-background);
  transition: --ripple-radius 0.3s ease;
}

Whenever --ripple-radius or --ripple-color changes—via hover states, CSS transitions, CSS animations, or main-thread JavaScript manipulation—the browser marks the paint layer as dirty and automatically invokes the worklet's paint() method to re-render the updated frame.

Key Performance Advantages

The CSS Paint API provides significant performance improvements over conventional dynamic styling techniques: