Most demos using the Paint worklet from Houdini are using shapes, as in slanted backgrounds, simple tooltip, corner shapes and so on.

This SVG-in-CSS worklet is a generic paint worklet that draw an SVG path, authored right from the CSS.

It needs some custom properties:

  • --svg-path: path() containing an SVG path
  • --svg-viewbox to define the SVG viewBox (default 0 0 100 100)
  • --svg-fill the fill color (default black)
  • --svg-stroke the stroke color (default transparent)
  • --svg-stroke-width the stroke width

I don’t add --svg-preserve-aspect-ratio because it complexifies the worklet, and above all, behaviour is already different between CSS and SVG. So, it’s better to play with background properties to deal with background size, position, repetition, etc.

Code samples below are live editable!

Basic shapes

Viewbox will adapt to element’s size, with default to 0 0 100 100.

.el {
  background-image: paint(svg-in-css);
.el--1 {
  --svg-path: path('M 10,63 H 50');
  --svg-stroke: gold;
  --svg-stroke-width: 10;
.el--2 {
  --svg-viewbox: 0 0 10 12;
  --svg-path: path('M 3,3 L 8,5 L 2,9');
  --svg-fill: deeppink;

Play with background-* properties to control how shape is rendered (fixed ratio for example).

.el--3 {
  --svg-path: path('m 71,13 c 13,0 24,11 24,26 c 0,26 -44,51 -44,51 s -44,-26 -44,-51 c 0,-14 11,-26 24,-26 l 0,0 c 8,0 16,4 20,12 c 4,-7 12,-12 20,-12 z');
  --svg-fill: limegreen;
  background-size: 80px 80px;
  background-position: 50% 50%;
  background-repeat: no-repeat;
.el--4 {
  --svg-path: path('M 71,19 a 12,12,0,0,1,10,10 l 5,34 a 12,12,0,0,1,-6,12 l -31,16 A 12,12,0,0,1,35,90 L 10,65 a 12,12,0,0,1,-2,-14 l 16,-31 a 12,12,0,0,1,12,-6');
  --svg-fill: deepskyblue;
  background-size: 100px 100px;
  background-position: 15% 40%;
  background-repeat: no-repeat;
.el--5 {
  --svg-path: path('M 40,0 L 0,100 h 20 L 60,0 z m 30,0 L 30,100 h 20 L 90,0 z');
  --svg-fill: orangered;
  background-size: 60px 60px;
  background-position: 40PX;
  background-repeat: no-repeat;

Using as CSS masks

As the SVG path is drawn on a canvas inside the worklet, you can’t really use CSS gradients from your CSS. Or we need to convert CSS gradients to canvas gradients.

Instead, set the paint() to a CSS mask on an element with a gradient! (because we don’t have background-mask neither)

.bg {
  /* apply a gradient to a background element */
  background: linear-gradient(#ff3030, #490080);

  /* create path */
  --svg-path: path('m27,12c17,-5 27,-4 30,7c4,11 -8,26 -1,35c7,9 16,-11 32,-7c16,3 7,28 -9,38c-16,10 -25,10 -26,-11c-1,-21 -36,-5 -45,-21c-9,-16 2,-35 19,-41z');
  /* and use as mask (sorry, Chrome still need prefixes :( ) */
  -webkit-mask: paint(svg-in-css);
         mask: paint(svg-in-css);
  -webkit-mask-size: 120px 120px;
         mask-size: 120px 120px;
  -webkit-mask-repeat: no-repeat;
         mask-repeat: no-repeat;
  -webkit-mask-position: 80%;
         mask-position: 80%;

With animations

As I said before, there is no defined SVG path data type in Properties & Values spec. Thus, you can’t register new properties with @property to make them animatable.

As a workaround, you can register simple custom properties (aka numbers), then combine them inside path() function! It uses the backtick trick explained in the JS-in-CSS demo. Not very convenient, but you can use raw CSS transitions and animations on these properties.

And this is fun!

Hover the first sample to see transition on path (click on it if on mobile) and see code below. The second one is using a CSS animation.

/* custom property for x position */
@property --pos {
  syntax: '<number>';
  inherits: false;
  initial-value: 0;
  /* set initial position */
  --pos: 30;
  /* use inside path */
  --svg-path: path(`M 10,63 H var(--pos)`);
  --svg-stroke: gold;
  --svg-stroke-width: 10;
  /* animate it on hover! */
  transition: --pos 1s;
.el--7:hover {
  --pos: 60;

Some notes

--svg-path and --svg-viewbox can’t be registered as valid properties. For the first one, there is no SVG path data type, and for the second, it seems that <number>+ is not supported in any browsers (unless is saying the opposite).

The main limitation of this approach is that only one path can be drawn, no full SVG. Imagine how cool it would be if we could use the full power of SVG that way! 🤩

A workaround could be to allow multiple paths, using arguments from the paint() function (to set color, size, etc.) instead of custom properties. That would have enabled us to draw multiple path using different values on one unique element. The point is, support is not great (again, is misleading). See the inner borders demo for an example (check your browser and your flags)