<FreeTransform />
Draws interactive resize and rotate handles around a single element, so users
can drag the corners/edges to resize, drag the top handle to rotate, and snap
to a grid. Wraps the JointJS+ ui.FreeTransform widget and renders no DOM of
its own — the handles are managed imperatively on the paper.
Use it in either mode:
- Outside
renderElement: pass the target via thecellprop, usually driven by selection state. Passnullto detach the handles. - Inside
renderElement: omitcell; the rendered element is targeted automatically from context.
Only elements get handles — a link cell is ignored with a dev warning.
Constrain behavior with props like minWidth/maxWidth,
preserveAspectRatio, resizeDirections, and allowRotation, or reach any
native option through the FreeTransformOptions options escape hatch.
See FreeTransformProps for every supported prop.
See
Examples
Attach to the selected element
import { GraphProvider, Paper, FreeTransform } from '@joint/react-plus';
import { useState } from 'react';
const cells = [{ id: '1', type: 'element', position: { x: 40, y: 40 }, size: { width: 80, height: 40 } }];
function Editor() {
const [selectedId, setSelectedId] = useState<string | null>(null);
return (
<GraphProvider initialCells={cells}>
<Paper
renderElement={() => <rect width={80} height={40} fill="#3498db" />}
onElementPointerClick={({ model }) => setSelectedId(String(model.id))}
/>
{selectedId && <FreeTransform cell={selectedId} preserveAspectRatio />}
</GraphProvider>
);
}
Inside renderElement (no cell needed)
import { GraphProvider, Paper } from '@joint/react';
import { FreeTransform } from '@joint/react-plus';
const cells = [{ id: '1', type: 'element', position: { x: 40, y: 40 }, size: { width: 80, height: 40 } }];
// Targets the rendered element automatically — no `cell` prop needed.
<GraphProvider initialCells={cells}>
<Paper renderElement={() => <FreeTransform allowRotation rotateAngleGrid={15} />} />
</GraphProvider>;
Props
allowOrthogonalResize?
readonly optional allowOrthogonalResize?: boolean;
Allow resizing from the edge (orthogonal) handles, not just the corners.
Default
true
allowRotation?
readonly optional allowRotation?: boolean;
Show the rotate handle and allow rotating the element.
Default
true
cell?
readonly optional cell?: ID | Cell<Attributes<Selectors>, ModelSetOptions> | null;
Cell to attach the handles to — a dia.Cell instance or its id (only
elements get handles; a link is ignored with a dev warning). Required
outside renderElement; pass null to detach. Inside renderElement
leave it unset so the rendered element is targeted from context — passing
cell there logs a dev warning and renders nothing.
maxHeight?
readonly optional maxHeight?: SizeConstraint;
Largest allowed height: a number, or a callback returning the limit per cell/direction.
Default
Infinity
maxWidth?
readonly optional maxWidth?: SizeConstraint;
Largest allowed width: a number, or a callback returning the limit per cell/direction.
Default
Infinity
minHeight?
readonly optional minHeight?: SizeConstraint;
Smallest allowed height: a number, or a callback returning the limit per cell/direction.
Default
0
minWidth?
readonly optional minWidth?: SizeConstraint;
Smallest allowed width: a number, or a callback returning the limit per cell/direction.
Default
0
options?
readonly optional options?: Partial<Options>;
Raw ui.FreeTransform.Options passthrough for anything joint-react-plus
doesn't expose as a dedicated prop (mvc internals like theme, events,
attributes, tagName, etc.). See FreeTransformOptions.
Avoid overriding options the component manages itself
(cell, cellView, paper).
padding?
readonly optional padding?: Padding;
Gap, in pixels, between the element's bounding box and the transform frame.
Default
3
preserveAspectRatio?
readonly optional preserveAspectRatio?: boolean;
Lock the element's aspect ratio while resizing from a corner.
Default
false
resizeDirections?
readonly optional resizeDirections?: Direction[];
Restrict the visible handles to these directions (e.g.
['bottom-right', 'bottom-left'] keeps only the two bottom corners). Omit
to show all eight. Valid values: 'top-left', 'top', 'top-right',
'right', 'bottom-right', 'bottom', 'bottom-left', 'left'.
resizeGrid?
readonly optional resizeGrid?: object;
Snap resizing to this { width, height } step. Omit to fall back to the
paper's gridSize (free resizing only when that is 1).
height
height: number;
width
width: number;
rotateAngleGrid?
readonly optional rotateAngleGrid?: number;
Snap rotation to multiples of this angle, in degrees.
Default
15
useBordersToResize?
readonly optional useBordersToResize?: boolean;
Use the element's borders as resize handles instead of dedicated handle markers.
Default
false
usePaperScale?
readonly optional usePaperScale?: boolean;
Scale the handles with the paper zoom so they keep a constant on-screen size.
Default
false