Transforms & Bounding Boxes
Every layer in a shader can be positioned, sized, and rotated inside the canvas with a bounding box — a rectangular frame, just like a layer in a design tool. You move it, resize it, and rotate it, and the layer is drawn to fit.
This is different from CSS transforms. A CSS transform moves the canvas element in the DOM layout. A bounding box positions the content within the canvas — the layer shifts, scales, or rotates without touching the DOM.
<!-- 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: { unit: 'uv', value: 0.25 },
y: { unit: 'uv', value: 0.25 },
width: { unit: 'uv', value: 0.5 },
height: { unit: 'uv', value: 0.5 },
origin: 'top-left',
rotation: 0
}" />
</Shader>
The boundingBox prop
Every renderable component accepts a boundingBox object. It has four required dimensions — x, y, width, height — plus an optional origin, a rotation, and a couple of refinements.
| Field | Type | Default | Description |
|---|---|---|---|
x | { value, unit } | — | Horizontal position of the box's anchor corner, measured in the origin frame |
y | { value, unit } | — | Vertical position of the box's anchor corner, measured in the origin frame |
width | { value, unit } | — | Box width |
height | { value, unit } | — | Box height |
origin | string | 'top-left' | Which reference point x / y are measured from — see Origin |
rotation | number | 0 | Rotation in degrees, around the box's geometric center |
cornerRadius | { value, unit } | — | Rounds the corners of the clipped region (filter layers only) |
lockAspect | boolean | false | Editor-only hint that locks the resize handles to the box's aspect ratio. Ignored at render time |
Each of x, y, width, height (and cornerRadius) is a dimensional value — a { value, unit } pair, so you can mix relative and absolute units on the same layer.
When no boundingBox is set, a layer fills the whole canvas. Setting a box is purely opt-in.
Units: uv (%) vs px
Each dimension carries its own unit:
| Unit | Meaning |
|---|---|
uv | A fraction of the canvas, 0–1. x/width are fractions of the canvas width; y/height of the canvas height. 0.5 = halfway / half the canvas. The Design Editor shows this as a percentage (50%). |
px | Device pixels. A fixed size that stays constant as the canvas resizes — the renderer re-resolves px → uv on every resize. Good for fixed-size logos, badges, and UI chrome. |
<!-- Relative box: always a centered quarter of the canvas, at any size -->
<Glass :bounding-box="{
x: { unit: 'uv', value: 0.25 },
y: { unit: 'uv', value: 0.25 },
width: { unit: 'uv', value: 0.5 },
height: { unit: 'uv', value: 0.5 },
origin: 'top-left',
rotation: 0
}" />
<!-- Fixed box: a 200×200px square, 24px from the top-left, regardless of canvas size -->
<Glass :bounding-box="{
x: { unit: 'px', value: 24 },
y: { unit: 'px', value: 24 },
width: { unit: 'px', value: 200 },
height: { unit: 'px', value: 200 },
origin: 'top-left',
rotation: 0
}" />
Because each property has its own unit, you can mix them — for example a fixed-height bar that always spans the full width:
<LinearGradient :bounding-box="{
x: { unit: 'uv', value: 0 },
y: { unit: 'px', value: 0 },
width: { unit: 'uv', value: 1 },
height: { unit: 'px', value: 80 },
origin: 'top-left',
rotation: 0
}" />
Origin
The origin is the reference point that x and y are measured from — the box's anchor corner and the frame those coordinates live in. It's a 3×3 grid of nine positions:
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 picks the X reference (left → from the left edge, right → from the right edge, center → a signed offset from the horizontal center). The vertical part does the same for Y (top, bottom, or center). The bare value center centers both axes.
'top-left'(default) —x/yare measured rightward/downward from the top-left corner.'top-right'—xis measured leftward from the right edge;ydownward from the top.'bottom-right'—xleftward from the right,yupward from the bottom.'center'—x/yare signed offsets from the canvas center (0, 0centers the box).
This makes "pin to a corner" trivial and resize-stable. A badge 24px in from the top-right corner stays glued there at any canvas size:
<ImageTexture
url="/badge.svg"
:bounding-box="{
x: { unit: 'px', value: 24 },
y: { unit: 'px', value: 24 },
width: { unit: 'px', value: 96 },
height: { unit: 'px', value: 96 },
origin: 'top-right',
rotation: 0
}"
/>
Origin vs. rotation pivot:
originonly controls wherex/yare measured from. Rotation always pivots around the box's own geometric center, independent of the origin you choose.
Rotation
rotation spins the box (and its content) around its center, in degrees. Positive values rotate clockwise.
<Stripes :bounding-box="{
x: { unit: 'uv', value: 0.2 },
y: { unit: 'uv', value: 0.2 },
width: { unit: 'uv', value: 0.6 },
height: { unit: 'uv', value: 0.6 },
origin: 'top-left',
rotation: 30
}" />
How different layers fill the box
The box means "draw this layer here," but exactly how a layer fills its box depends on the kind of layer:
- Generators (gradients, noise, patterns, textures, etc.) 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 rectangle. (A handful of screen-anchored patterns simply confine to the box instead of re-fitting.)
- Shapes (Circle, Star, Blob, and the SDF shape effects like Glass and Crystal) drive their native position/size/rotation from the box, so the shape itself moves and scales with the frame.
- Filters & distortions (Blur, Glitch, distortions, …) clip to the box — the effect is confined to the rectangle, and the untouched layer beneath shows through outside it. This is where
cornerRadiusapplies, rounding the clipped region.
In all cases the box is a rotated rectangle, so rotation and corner rounding behave consistently.
Basic usage across frameworks
Animating a bounding box
boundingBox is a reactive prop — bind any field to state and it updates live, no recompile:
For continuous animation, you can also reach for a Dynamic Prop on the underlying layer instead of driving rotation from component code.