CSS Paint API (Houdini)
paint() method whenever it needs to paint that element, handing it a drawing context that works much like the <canvas> 2D API.Registering a worklet
paint() method and registers it under a name with registerPaint().// dashed-border-worklet.js
class DashedBorderPainter {
static get inputProperties() {
// Custom properties this worklet wants to read from the
// element it's painting — lets CSS parameterize the drawing.
return ['--dash-color', '--dash-gap']
}
paint(ctx, size, properties) {
const color = properties.get('--dash-color').toString().trim() || '#0066cc'
const gap = parseFloat(properties.get('--dash-gap')) || 8
ctx.strokeStyle = color
ctx.lineWidth = 3
ctx.setLineDash([gap, gap])
ctx.strokeRect(4, 4, size.width - 8, size.height - 8)
}
}
registerPaint('dashed-border', DashedBorderPainter)Loading the worklet and using it from CSS
// In your page's regular JavaScript
if ('paintWorklet' in CSS) {
CSS.paintWorklet.addModule('/dashed-border-worklet.js')
}.decorative-card {
--dash-color: #cc6600;
--dash-gap: 6;
/* Use the registered worklet name just like an image */
background-image: paint(dashed-border);
padding: 1.5rem;
}inputProperties, ordinary custom properties become the parameter-passing mechanism between CSS and JavaScript. Different elements using the same worklet can look completely different just by setting different values for those custom properties — no separate worklet needed per variant.Worked example: a custom-drawn decorative border pattern
A design system that needs a repeating hand-drawn-looking border, a halftone-dot pattern, or a generative noise texture behind a card traditionally has two options: ship a pre-rendered PNG/SVG asset (which doesn't adapt to the element's actual size or theme colors without generating multiple variants), or draw it with JavaScript onto a positioned canvas element sitting behind the content (extra DOM, extra layout bookkeeping). A paint worklet draws procedurally, at whatever size the element actually is, parameterized by ordinary CSS custom properties — closer to how an SVG pattern behaves, but with the full expressiveness of an imperative drawing API.
The worklet re-runs automatically whenever the element is resized or an input custom property it declared changes — no manual redraw logic to write.
Drawing happens off the main JS thread in a dedicated paint worklet context, so it does not block script execution the way a canvas redraw loop can.
Because it plugs into
background-image, it composes with everything elsebackgroundsupports — position, size, repeat.