Transforms
Position, size, and rotate any layer inside the canvas with a bounding box, in any CSS unit.
A bounding box is a rectangular frame for one layer, like a layer's frame in Figma. You move, resize, and rotate the frame, and the layer is drawn to fit it.
A CSS transform moves the whole canvas element in the page. A bounding box moves one layer inside the canvas and leaves the DOM alone. The two side by side:
<!-- CSS transform: moves the whole canvas element in the page -->
<Shader class="-translate-x-4">...</Shader>
<!-- Bounding box: frames one layer inside the canvas -->
<Shader>
<LinearGradient :bounding-box="{ x: '25%', y: '25%', width: '50%', height: '50%' }" />
</Shader>
Setting the boundingBox prop
Every renderable component accepts a boundingBox object. Without one, the layer fills the whole canvas. Its fields:
| Field | Type | Default | Description |
|---|---|---|---|
x | length | 0% | Horizontal position of the box's anchor corner, measured from the origin. |
y | length | 0% | Vertical position of the box's anchor corner, measured from the origin. |
width | length | 100% | Box width. |
height | length | 100% | Box height. |
origin | string | 'top-left' | Which reference point x and y are measured from. See Anchoring to an edge or corner. |
rotation | number | 0 | Rotation in degrees, around the box's center. |
cornerRadius | length | none | Rounds the corners of the clipped region. Filter layers only. Doesn't accept %. |
lockAspect | boolean | false | Design editor hint that locks the resize handles to the box's aspect ratio. The renderer ignores it. |
A length is a number with a unit, such as '50%', '24px', or '10vw'. A bare number means pixels. Sizing and units covers every unit and what it measures against. Fields you leave out take the defaults, which together cover the full canvas.
Mixing units in one box
Each field takes its own unit. A relative box and a fixed one:
<!-- Relative box: always a centered quarter of the canvas, at any size -->
<Glass :bounding-box="{ x: '25%', y: '25%', width: '50%', height: '50%' }" />
<!-- Fixed box: a 200×200px square, 24px from the top-left, at any canvas size -->
<Glass :bounding-box="{ x: 24, y: 24, width: 200, height: 200 }" />
Units can differ within one box. This bar is always 80px tall and spans the full width:
<LinearGradient :bounding-box="{ width: '100%', height: 80 }" />
A field can also be an object with a value and a unit. You'll also see the uv unit, a fraction of the canvas from 0 to 1: { value: 0.5, unit: 'uv' } is the same as '50%'.
Anchoring to an edge or corner
The origin is the reference point x and y are measured from. It picks both the canvas edge you measure from and the corner of the box that sits at that distance, the way CSS right: 24px measures from the right edge to the element's right edge. There are nine origins, in a 3×3 grid:
top-left top-center top-right
center-left center center-right
bottom-left bottom-center bottom-right
The two axes are independent. The horizontal part sets the X reference: left measures from the left edge, right from the right edge, and center gives a signed offset from the horizontal center. The vertical part does the same for Y with top, bottom, or center. The bare value center centers both axes.
top-left(default):xis measured rightward from the left edge,ydownward from the top.top-right:xis measured leftward from the right edge,ydownward from the top.bottom-right:xis measured leftward from the right edge,yupward from the bottom.center:xandyare signed offsets from the canvas center.0, 0centers the box.
Combined with px units, an origin keeps a layer pinned to a corner at any canvas size. This badge stays 24px in from the top-right corner:
<ImageTexture
url="/badge.svg"
:bounding-box="{ x: 24, y: 24, width: 96, height: 96, origin: 'top-right' }"
/>
Rotating a layer
rotation turns the box and its content around the box's center, in degrees. Positive values rotate clockwise. The pivot is always the center, whatever the origin: unlike CSS transform-origin, origin only sets where x and y are measured from. A 30° turn:
<Stripes :bounding-box="{ x: '20%', y: '20%', width: '60%', height: '60%', rotation: 30 }" />
Knowing how each layer fills the box
How a layer fills its box depends on the kind of layer:
- Generators (gradients, noise, patterns, textures): re-fit into the box. The effect lays itself out as if the canvas had been resized to the box, undistorted, then confined to the box. A few screen-anchored patterns are confined to the box without re-fitting.
- Shapes (Circle, Star, Blob, and shape effects such as Glass and Crystal): take their own position, size, and rotation from the box, so the shape itself moves and scales with it.
- Filters and distortions (Blur, Glitch, distortions): clip to the box. The effect stays inside the rectangle, and the unaffected layer beneath shows through outside it.
cornerRadiusrounds this clipped region.
In every case the box is a rotated rectangle, so rotation and corner rounding behave the same way.
Setting a box in each framework
The same rotated box in every framework:
Animating a bounding box
boundingBox is reactive. Bind any field to state and the box updates on the next frame without recompiling, as long as it doesn't switch to or from covering the full canvas. Crossing that boundary recompiles, because a full-canvas box is treated as no box at all. This box spins half a degree per frame:
For continuous motion, a dynamic prop on the layer's own props can replace the animation loop.
Next steps
- Sizing and units: every unit a length can use, and what each measures against.
- Dynamic props: animate props or drive them from the cursor without animation code.
- Shape effects: place Glass, Neon and other effects inside 2D and 3D shapes.
- Layout and positioning: size and place the canvas itself in your page.