/ component

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.

/ basic-usage

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)
/ 3d-preview

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.

3D preview
perspective(600px) rotateY(35deg) rotateX(-10deg) translateZ(20px)
3D preview
perspective(600px) rotateY(35deg) rotateX(-10deg) translateZ(20px)
/ types

Three usage tiers

From useState-and-go to per-function unit-typed dispatch.

01 casual
string

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)")
02 intellisense
TransformString

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)")
03 strict
TransformLiteral<S>

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

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.

PropTypeDescription
valueTransformString | (string & {})Current CSS transform value. Required. `none` is the empty state.
onChange(next: TransformString) => voidFires 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 to never at the strict tier (undecidable at compile time). The runtime parser accepts them β€” use the casual / IntelliSense tier.
/ install

Drop it in

One command via the shadcn CLI.

$ pnpm dlx shadcn@latest add https://turtiesocks.github.io/ridiculous/r/transform-builder.json