Paints

September 11, 2026 · View on GitHub

Internally a node's fill is not a property but a paint child: a sub-entity appended to the geometry, exactly like the editor's fill list. The fill prop is shorthand for a solid paint; declaring paints as JSX children exposes the full model, including gradients:

<rect width={640} height={360} cornerRadius={24}>
  <linearGradientPaint rotation={90}>
    <colorStop offset={0} color="#FF0055" />
    <colorStop offset={1} color="#0055FF" />
  </linearGradientPaint>
</rect>

Paint elements are valid inside any filled visual element (<rect>, <text>, <textRange>, <video>, <image>, <html>, <surface>, and a <scene>); a <group> has no fill of its own, so it takes none. Multiple paints stack in document order; later paints render on top, and a paint child on a <video>/<image> draws over the media paint created by src.

ElementPropsMeaning
<solidPaint>color (required), opacitySolid fill; equivalent to the fill prop.
<linearGradientPaint>rotation, opacityLinear gradient across the parent's box; rotation in degrees, 0 = left to right.
<radialGradientPaint>rotation, opacityRadial gradient centered in the parent's box.
<colorStop>offset (required, 01), color (required), opacityGradient color stop. Valid only inside gradient paints, which take no other children.
<imagePaint> / <videoPaint>src (required), objectFit, frameRate, opacityMedia painted into the parent's box — see below.
<htmlPaint>opacity, HTML childrenReactive HTML laid out and drawn into the parent's box (flagged Chromium API). <html> is shorthand for a <rect> carrying one.
<surfacePaint>opacity, refA canvas your ref draws into (any context type), sampled into the parent's box every frame. <surface> is shorthand for a <rect> carrying one.
<shaderPaint>wgsl (required), uniforms, opacityA WGSL fragment shader applied to the video/image paint directly below it (that media renders only through the shader's output), or run procedurally when there is none.

Every paint also takes blendMode and hidden, which mean on a paint what they mean on a node.

Colors accept any CSS color; alpha is ignored (use opacity). color, opacity, and offset are animatable with <keyframeTrack> children (see keyframes.md), so gradients can animate. Paints have no spatial or timing props of their own and cannot be roots.

Media paints

<imagePaint> and <videoPaint> are the same media a <video> or <image> element is, as a paint child: they fill something else with it, so a rect or a text can be filled with a picture.

<text fontSize={220} fontWeight="bold" width={1920} textAlign="center">
  OCEAN
  <videoPaint src="b-roll/waves.mp4" objectFit="cover" />
</text>

They take src exactly as the elements do (see media.md), plus objectFit and frameRate. Which tag it is only says what the source is expected to be: the paint follows what the src turns out to name, so a frames directory plays under either.

The media paint a <video> or <image> element creates from its own src is intrinsic — it sits at the bottom of that element's paint stack, beneath any paint child. Timing stays the element's; a media paint has none of its own.