CSS paint() Function

The CSS paint() function generates custom images using JavaScript code registered through the CSS Painting API.

It allows developers to create backgrounds, borders, and other graphical effects without using traditional image files. The function references a registered paint worklet, which draws the image using a canvas-like rendering context. Browser support is limited, so a fallback should be provided.

Syntax

paint(name)
paint(name, arguments)

The first argument identifies a registered paint worklet. Additional arguments can be supplied when the worklet declares support for them.

For example:

background-image: paint(custom-pattern);
background-image: paint(custom-border);
background-image: paint(custom-pattern, red);

The names custom-pattern and custom-border must match names registered by JavaScript.

The function produces a CSS <image> value and can be used with compatible properties.

For example:

.box {
  background-image: paint(custom-pattern);
}

The browser invokes the registered paint worklet to generate the background image.

The paint() function does not contain drawing instructions itself. Those instructions are defined in JavaScript.

Parameters

The paint() function accepts a required worklet name and optional additional arguments.

Parameter Description
name The registered name of the paint worklet that generates the image.
arguments Optional comma-separated values passed to the worklet when its inputArguments definition supports them.

For example:

paint(dot-pattern)

This references a paint worklet registered with the name dot-pattern.

Another example supplies an argument:

paint(dot-pattern, 12px)

The worklet must declare and process the additional argument for this form to work.

The worklet name is a CSS identifier, not a quoted string or a JavaScript filename.

For example, the following is not the correct way to reference a worklet:

background-image: paint("dot-pattern.js");

Instead, register the worklet using JavaScript and reference its registered name in CSS.

How It Works

The paint() function connects CSS image rendering with JavaScript drawing instructions.

The process involves four main steps:

  1. Create a JavaScript paint worklet containing a drawing class.
  2. Register the drawing class using registerPaint().
  3. Load the worklet using CSS.paintWorklet.addModule().
  4. Reference the registered name using the CSS paint() function.

For example:

.box {
  background-image: paint(dot-pattern);
}

When the browser needs to render the background, it uses the registered worklet to generate the image.

The worklet receives information about the image dimensions and the CSS properties it has declared as inputs.

The generated image can respond to changes in element size and supported CSS property values.

The browser manages when the paint code runs. Developers do not manually call the worklet's drawing method for each frame.

CSS Painting API

The CSS Painting API is part of the CSS Houdini collection of browser APIs.

It allows custom JavaScript drawing code to generate images that can be used in CSS.

For example, a generated image can be applied as a background:

.panel {
  background-image: paint(custom-pattern);
}

It can also be used with properties that accept CSS image values, subject to the property's requirements and browser support.

The Painting API provides a specialized drawing context similar to the HTML Canvas 2D context, but it does not expose every Canvas API feature.

Unlike a regular canvas element, a paint worklet generates an image for use within CSS styling.

The API is designed for custom rendering rather than general DOM manipulation.

Paint worklets run in a restricted environment and cannot directly access the document or ordinary browser window objects.

Paint Worklets

A paint worklet is a JavaScript module containing a class that defines how an image should be drawn.

For example:

class SolidPattern {
  paint(ctx, size) {
    ctx.fillStyle = "#0066cc";
    ctx.fillRect(0, 0, size.width, size.height);
  }
}
registerPaint("solid-pattern", SolidPattern);

The class contains a paint() method that draws a blue rectangle.

The registerPaint() function associates the class with the name solid-pattern.

The registered name can then be used in CSS:

.box {
  background-image: paint(solid-pattern);
}

The JavaScript method named paint() is different from the CSS paint() function.

The JavaScript method performs the drawing, while the CSS function requests the generated image.

Registering Paint Worklets

Paint worklets must be registered before their generated images can be rendered.

First, create a JavaScript module named pattern.js:

class PatternPainter {
  paint(ctx, size) {
    ctx.fillStyle = "#0066cc";
    ctx.fillRect(0, 0, size.width, size.height);
  }
}
registerPaint("custom-pattern", PatternPainter);

Next, load the module from the main document's JavaScript:

if ("paintWorklet" in CSS) {
  CSS.paintWorklet.addModule("pattern.js");
}

The addModule() method loads the JavaScript module into the paint worklet environment.

After registration, the worklet can be referenced in CSS:

.box {
  background-image: paint(custom-pattern);
}

The filename and registered worklet name serve different purposes.

The filename identifies the JavaScript module to load. The registered name identifies the drawing class used by CSS.

Worklet modules must be loaded from an appropriate secure environment, such as HTTPS or a supported localhost development server.

Opening the example directly using a file:// URL may not work.

The paint() Method

A paint worklet class defines a JavaScript method that receives the drawing context, image dimensions, computed CSS properties, and optional arguments.

Its general structure is:

paint(ctx, size, properties, args) {
  // Drawing instructions
}

The parameters are:

Parameter Description
ctxThe drawing context used to create the image.
sizeAn object containing the image's width and height.
propertiesA map of the computed CSS property values declared by the worklet.
argsOptional values supplied through the CSS paint() function.

For example:

class CirclePainter {
  paint(ctx, size) {
    const radius = Math.min(size.width, size.height) / 4;
    ctx.fillStyle = "#0066cc";
    ctx.beginPath();
    ctx.arc(
      size.width / 2,
      size.height / 2,
      radius,
      0,
      Math.PI * 2
    );
    ctx.fill();
  }
}
registerPaint("circle-pattern", CirclePainter);

This worklet draws a blue circle centered within the generated image.

The circle size is calculated from the available image dimensions.

The worklet can be used as a CSS background:

.box {
  background-image: paint(circle-pattern);
}

The browser determines the dimensions supplied to the worklet based on the CSS image's rendering context.

Using CSS Custom Properties

Paint worklets can read selected computed CSS properties, including custom properties.

The worklet must declare the properties it uses through a static inputProperties getter.

For example:

class ColorPainter {
  static get inputProperties() {
    return ["--paint-color"];
  }
  paint(ctx, size, properties) {
    const color = properties.get("--paint-color").toString().trim();
    ctx.fillStyle = color;
    ctx.fillRect(0, 0, size.width, size.height);
  }
}
registerPaint("color-pattern", ColorPainter);

The worklet reads the computed value of --paint-color and uses it as the fill color.

In CSS:

.box {
  --paint-color: #0066cc;
  background-image: paint(color-pattern);
}
.box:hover {
  --paint-color: #00a896;
}

The background can be repainted when the declared custom property changes.

Custom properties allow the same worklet to generate different results for different elements.

Provide valid property values and handle missing or unexpected values in production worklets.

Passing Function Arguments

The CSS paint() function can accept additional arguments when the registered worklet declares their expected syntax.

For example:

background-image: paint(circle-color, blue);

The worklet must declare its argument types using inputArguments.

For example:

class CircleColorPainter {
  static get inputArguments() {
    return ["<color>"];
  }
  paint(ctx, size, properties, args) {
    const color = args[0].toString();
    const radius = Math.min(size.width, size.height) / 4;
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.arc(
      size.width / 2,
      size.height / 2,
      radius,
      0,
      Math.PI * 2
    );
    ctx.fill();
  }
}
registerPaint("circle-color", CircleColorPainter);

The worklet accepts a CSS color argument and uses it to draw the circle.

The corresponding CSS is:

.blue-circle {
  background-image: paint(circle-color, blue);
}
.red-circle {
  background-image: paint(circle-color, red);
}

The two elements use the same drawing class but supply different colors.

Support for additional paint function arguments can vary by browser implementation. Custom properties are another way to pass values to a paint worklet.

Responsive Generated Images

A paint worklet can use the available image dimensions to generate graphics that adapt to the size of the rendered image.

For example:

class DiagonalPainter {
  paint(ctx, size) {
    ctx.strokeStyle = "#0066cc";
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(size.width, size.height);
    ctx.stroke();
  }
}
registerPaint("diagonal-line", DiagonalPainter);

The line extends from the top-left corner to the bottom-right corner of the generated image.

In CSS:

.box {
  width: 100%;
  min-height: 150px;
  background-image: paint(diagonal-line);
}

When the image dimensions change, the browser can generate a new drawing using the updated width and height.

This makes paint worklets useful for graphics that need to adapt to different layout sizes.

The worklet draws within the image dimensions supplied by the browser. It does not directly control the element's layout size.

Fallbacks and Feature Detection

Because CSS Painting API support is limited, provide a standard CSS background or another supported image as a fallback.

For example:

.box {
  background-color: #0066cc;
  background-image: linear-gradient(135deg, #0066cc, #00a896);
}
@supports (background-image: paint(custom-pattern)) {
  .box {
    background-image: paint(custom-pattern);
  }
}

The gradient is used when the browser does not support the custom paint image.

The @supports rule checks whether the browser recognizes the CSS declaration.

JavaScript feature detection can also be used:

if ("paintWorklet" in CSS) {
  CSS.paintWorklet.addModule("pattern.js");
}

This checks for the Painting API before attempting to load the module.

Feature detection does not guarantee that a worklet has loaded successfully or that its drawing code is valid.

For production use, handle module loading failures and retain a useful fallback.

Also remember that the worklet name must be registered before the browser can render the generated image.

Example

This example creates a repeating dot pattern using a JavaScript paint worklet.

It requires a separate JavaScript file and a browser that supports the CSS Painting API.

HTML:

<div class="paint-demo">
  Custom Dot Pattern
</div>
<script>
  if ("paintWorklet" in CSS) {
    CSS.paintWorklet.addModule("dot-pattern.js");
  }
</script>

CSS:

.paint-demo {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 180px;
  padding: 20px;
  background-color: #e9f2ff;
  background-image: radial-gradient(#0066cc 2px, transparent 2px);
  background-size: 20px 20px;
  color: #111111;
  text-align: center;
  font-size: 16px;
  font-weight: bold;
}
@supports (background-image: paint(dot-pattern)) {
  .paint-demo {
    background-image: paint(dot-pattern);
    background-size: auto;
  }
}

JavaScript - dot-pattern.js:

class DotPatternPainter {
  paint(ctx, size) {
    const spacing = 20;
    const radius = 2;
    ctx.fillStyle = "#0066cc";
    for (let x = 10; x < size.width; x += spacing) {
      for (let y = 10; y < size.height; y += spacing) {
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2);
        ctx.fill();
      }
    }
  }
}
registerPaint("dot-pattern", DotPatternPainter);

The JavaScript worklet draws small blue circles across the available image area.

The CSS paint(dot-pattern) function references the registered drawing class.

In browsers without Painting API support, the CSS radial gradient provides a similar dotted background.

The generated pattern is decorative. The text remains ordinary HTML content.

Browser Result:

The following preview demonstrates the fallback appearance. It does not execute the paint worklet.

Browser Result - CSS Fallback
Custom Dot Pattern

To test the actual worklet, save the JavaScript in dot-pattern.js, load the HTML page through HTTPS or localhost, and use a compatible browser.

Custom Properties Example

This example creates a custom striped background that reads its color from a CSS custom property.

CSS:

.paint-stripes {
  --stripe-color: #0066cc;
  min-height: 150px;
  background-color: #ffffff;
  background-image: repeating-linear-gradient(
    45deg,
    #0066cc 0 10px,
    #ffffff 10px 20px
  );
}
@supports (background-image: paint(stripe-pattern)) {
  .paint-stripes {
    background-image: paint(stripe-pattern);
  }
}

JavaScript - stripe-pattern.js:

class StripePainter {
  static get inputProperties() {
    return ["--stripe-color"];
  }
  paint(ctx, size, properties) {
    const color = properties.get("--stripe-color").toString().trim();
    const stripeWidth = 10;
    ctx.fillStyle = "#ffffff";
    ctx.fillRect(0, 0, size.width, size.height);
    ctx.fillStyle = color;
    for (let x = -size.height; x < size.width; x += stripeWidth * 2) {
      ctx.beginPath();
      ctx.moveTo(x, 0);
      ctx.lineTo(x + stripeWidth, 0);
      ctx.lineTo(x + size.height + stripeWidth, size.height);
      ctx.lineTo(x + size.height, size.height);
      ctx.closePath();
      ctx.fill();
    }
  }
}
registerPaint("stripe-pattern", StripePainter);

JavaScript - Load the Worklet:

if ("paintWorklet" in CSS) {
  CSS.paintWorklet.addModule("stripe-pattern.js");
}

The worklet reads the --stripe-color property and uses it when drawing the stripes.

Different elements can use different custom property values:

.stripe-blue {
  --stripe-color: #0066cc;
}
.stripe-green {
  --stripe-color: #008866;
}

This allows one registered worklet to generate multiple color variations.

The CSS gradient provides a fallback, although the custom property only changes the worklet-generated stripes in this simplified example.

Common Uses

The paint() function is useful for generating decorative images directly from JavaScript drawing instructions.

Use Description
Custom backgroundsGenerate patterns, shapes, and other decorative background images.
Procedural patternsCreate dots, stripes, grids, and repeated geometric designs.
Responsive graphicsDraw images based on the available rendering dimensions.
Custom bordersGenerate image-based border effects where supported.
Design systemsReuse registered drawing logic across multiple elements.
Custom property effectsAdjust generated graphics using CSS values.
Experimental renderingCreate specialized graphical effects not directly available through standard CSS.

For simple shapes, gradients, and patterns, standard CSS is usually easier and more widely supported.

Use paint() when custom drawing logic provides a meaningful advantage over existing CSS features.

Limitations

The paint() function has several important requirements and limitations.

  • It requires a registered JavaScript paint worklet.
  • Browser support is limited and is not consistent across all major browser engines.
  • The Painting API requires a secure context.
  • The worklet must be loaded before its generated image can be rendered.
  • Paint worklets execute in a restricted JavaScript environment without direct DOM access.
  • The drawing context supports a subset of familiar Canvas 2D operations.
  • Additional function arguments require a compatible inputArguments declaration.
  • Custom properties must be declared through inputProperties to be supplied to the worklet.
  • Generated images do not replace semantic HTML content or accessible text.
  • Complex drawing operations can increase rendering costs.
  • A CSS or image fallback is recommended for unsupported browsers.

For example, this CSS requires a registered worklet:

background-image: paint(custom-pattern);

The declaration alone does not create a custom image.

The following is also not a valid way to load a worklet:

background-image: paint("pattern.js");

Worklet modules are loaded through JavaScript, not directly from a filename supplied to paint().

For production websites, provide a usable fallback and verify the feature in the browsers your audience uses.

Browser Support

The CSS paint() function and CSS Painting API have Limited availability and are not Baseline across major browsers.

Chromium-based browsers, including Chrome and Edge, support CSS Paint Worklets. Support is not consistently available in Firefox and Safari.

For example:

.box {
  background-color: #0066cc;
  background-image: paint(custom-pattern);
}

In browsers that do not support the function, the unsupported image declaration is ignored, leaving the background color.

A more complete fallback uses a standard CSS image:

.box {
  background-image: linear-gradient(135deg, #0066cc, #00a896);
}
@supports (background-image: paint(custom-pattern)) {
  .box {
    background-image: paint(custom-pattern);
  }
}

The custom worklet must also be registered and loaded successfully.

For current compatibility information, visit Can I Use? and search for CSS Painting API.

Specifications

The paint() function is defined in the CSS Painting API Level 1 .

The specification defines custom paint images, paint worklet registration, drawing contexts, and how worklets receive CSS property values.

Related CSS features include the background-image property, border-image-source property, CSS custom properties, and the @supports rule.