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:
ctx: A 2D rendering context providing standard drawing methods likefillRect(),arc(), andbeginPath().geometry: An object containing thewidthandheightof the area being drawn onto.properties: A style map used to read the CSS properties defined ininputProperties.
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.