Transform Builder
Edit the CSS transform property β a space-separated list of transform functions β with compile-time FUNCTION-NAME DISPATCH. Each function's name is looked up in a signature table that validates its argument count and every argument's dimension. rotate(10px) β never (wants angle); translateX(45deg) β never (wants length). The strict tier resolves any violation to never.
Build a transform list
Controlled value + onChange. Add functions, edit each argument with the right units, and the editor emits a valid space-separated transform string.
translateX(10px) rotate(45deg) scale(1.1)Drive a card in 3D
The scrubbers compose translate/ rotate/scale/skew functions into the typed string and write it back β the card's transform is exactly the value you see.
perspective(600px) rotateY(35deg) rotateX(-10deg) translateZ(20px)Three usage tiers
From useState-and-go to per-function unit-typed dispatch.
Pass any string
useState<string>. No compile-time validation; the runtime parser handles whatever you type β including calc() and var() arguments.
translate(10px, 20%) rotate(15deg)const [value, setValue] = useState<string>("translate(10px, 20%) rotate(15deg)")
Literal-shaped hints
State typed as TransformString β the union of every translate()/scale()/rotate()/β¦ head plus none. It is also the onChange return type.
scale(1.25)const [value, setValue] = useState<TransformString>("scale(1.25)")
Per-function unit typing at compile time
cssTransform() dispatches on each function name and resolves a wrong unit or arg count to never β a type error before you run the code.
translateX(10px) rotate(45deg) scale(1.5)cssTransform("translateX(10px) rotate(45deg)") // β // @ts-expect-error rotate wants an angle cssTransform("rotate(10px)") // @ts-expect-error matrix needs 6 numbers cssTransform("matrix(1, 0, 0, 1, 0)")
Note: calc() / var() inside an argument are undecidable at compile time β use the casual or IntelliSense tier for those.
API
Public surface β component props, runtime helpers, and the type exports.
Β§ TransformBuilder / TransformBuilderPanel
<TransformBuilder
value: TransformString | (string & {})
onChange: (next: TransformString) => void
className?: string
aria-label?: string
/>TransformBuilder is popover-wrapped; TransformBuilderPanel renders the same editor inline. Both are controlled.
| Prop | Type | Description |
|---|---|---|
| value | TransformString | (string & {}) | Current CSS transform value. Required. `none` is the empty state. |
| onChange | (next: TransformString) => void | Fires when the edited list changes. Emits the normalized space-separated string (or `none`). |
Β§ Sub-components
<TransformFunctionRow item onChange onRemove />
One editable function row β a grouped function select, the right per-argument editors, and a remove button. The dispatch made visible.
<ArgEditor fn label kind value onChange />
A single argument editor: a numeric field plus a unit select for length/angle slots; unitless for number/percentage slots.
<AddFunctionMenu onAdd />
Grouped select of every supported function; calls onAdd with the chosen name.
<TransformPreview3D value onChange? />
A perspective scene with a card driven by the value, plus translate/rotate/scale/skew slider scrubbers β the 3D showcase.
Β§ Runtime helpers
cssTransform<S extends string>(value: S & TransformLiteral<S>): S
Call-site validator. Mirrors cssCalc() / color() / easing().
parseTransform(src: string): TransformItem[] | null
String β typed items, or null on unknown-function / arity / syntax error. `none` and empty β []. Tolerates calc()/var() args.
formatTransform(items: TransformItem[]): string
Canonical re-serialization (single spaces). Empty list β `none`.
itemToCss(item: TransformItem): string
One item β its CSS function string.
transformFunctions(src: string): string[]
Runtime mirror of FunctionsOf β the function names in order.
defaultItem(fn): TransformItem
Seed a fresh row with sensible defaults (translateX(0px), rotate(0deg), scale(1), identity matrix, β¦).
argSpec(fn): { min, max, kinds, labels }The runtime dispatch table β arity range, per-slot dimension kinds, and axis labels that drive the UI.
Β§ Types
- TransformLiteral<S>
- Strict validator β S if every function validates (arity + arg dimensions), else never.
- TransformString
- Suggestion union: per-function heads + `none`.
- TransformStringMap / TransformFn
- Function β output-string map and its key union.
- TransformFunctionName
- Union of every supported function name.
- FunctionsOf<S>
- Tuple of the function names in a transform string.
- FunctionCountOf<S>
- Number of functions in the list.
- TransformItem
- Per-function discriminated-union state (exported for advanced use).
Β§ Strict-tier scope
- Full function-name dispatch:
SplitBySpaceβParseFunctionβ a signature table validating each function's arity and every argument's dimension (IsLength/IsAngle/IsNumber/IsPercentage). - Every function is fully validated, including the 16-argument
matrix3dβ a flat fold over the comma list stays well inside the compile budget. - Dimension + arity only β numeric values are not range-checked (no "perspective must be positive").
calc()/var()inside an argument resolve toneverat the strict tier (undecidable at compile time). The runtime parser accepts them β use the casual / IntelliSense tier.
Drop it in
One command via the shadcn CLI.
$ pnpm dlx shadcn@latest add https://turtiesocks.github.io/ridiculous/r/transform-builder.json