react-resizable
react-resizable adds mouse and touch drag handles to a React element so a user can change its pixel width and height. It offers a low-level controlled Resizable component and a simpler ResizableBox that keeps size in local state. Constraints, aspect-ratio locking, eight handle positions, drag-grid snapping, and scaled-parent correction cover the common panel and dashboard cases. It is a focused interaction component, not a layout system, splitter, ResizeObserver wrapper, or accessibility-complete widget.
A good focused choice for mouse and touch resizing when your React app already owns layout state. Skip it for accessible splitters, responsive track sizing, or full dashboard layout management unless you are prepared to build those layers yourself.
Use it if
- You need draggable resize handles on a React panel and want both a quick stateful component and a controlled building block
- You need minimum and maximum dimensions, fixed aspect ratios, grid snapping, or handles on specific edges and corners
- Your UI already owns the resulting width and height and can apply those pixel values to its layout
- You need a small focused primitive that works across React versions from 16.3 onward
- You need keyboard-accessible resizing out of the box: the package renders draggable span handles but documents no keyboard controls, ARIA separator behavior, or focus management, so you must build those yourself
- You want a full split-pane or dashboard layout: this package changes one element's dimensions and does not allocate space among siblings, persist layouts, or manage collision
- Your sizing model is percentages, flex fractions, or CSS grid tracks: the public callbacks and width/height props use numeric pixel dimensions, leaving conversion and responsive rules to your app
- You only need to observe size changes caused by layout or content: react-resizable responds to handle drags and is not a ResizeObserver abstraction
- You cannot load its CSS or reproduce it: the README says the supplied stylesheet is required for visible, working default handles, and importing the component alone is not enough
Setup reality
Installation is npm install react-resizable, but a working import is not the whole setup. React and React DOM 16.3 or newer are peer dependencies, while react-draggable and prop-types arrive as runtime dependencies. You must also import react-resizable/css/styles.css or copy that stylesheet into a bundler that cannot import CSS; without it, the default handles are invisible and do not work properly. Version 4 ships its own TypeScript declarations, so remove the old @types/react-resizable package or TypeScript may resolve competing declarations. ResizableBox is quickest, but its style width and height are deliberately ignored because internal state owns those values. Resizable is controlled: every onResize callback must update the width and height props and the child style, or the handle moves without a lasting size change. Custom handle components must forward the provided ref and remaining pointer props to a real DOM element, otherwise react-draggable cannot attach correctly and can report that DraggableCore was not mounted. Sizes and constraints are numeric pixels. A transformed parent needs the matching transformScale value, and grid snapping goes through draggableOpts. Finally, the default drag handle is pointer-oriented; keyboard behavior, ARIA labeling, persistence, responsive conversion, and neighboring layout updates remain application work.
Patterns
Render a stateful resizable boxbasic-resizable-box
import { ResizableBox } from 'react-resizable';
import 'react-resizable/css/styles.css';
export function Panel() {
return (
<ResizableBox width={320} height={180}>
<div>Drag the southeast handle</div>
</ResizableBox>
);
}The CSS import is required for the default handle to be visible and positioned correctly.
Keep size in parent statecontrolled-resizable
import { useState } from 'react';
import { Resizable } from 'react-resizable';
export function ControlledPanel() {
const [size, setSize] = useState({ width: 320, height: 180 });
return (
<Resizable
width={size.width}
height={size.height}
onResize={(_, data) => setSize(data.size)}
>
<div style={{ width: size.width, height: size.height }}>Content</div>
</Resizable>
);
}Resizable has no size state; update both its props and the child dimensions from each callback.
Set minimum and maximum dimensionsconstrain-size
<ResizableBox
width={300}
height={200}
minConstraints={[180, 120]}
maxConstraints={[720, 480]}
>
<div>Constrained content</div>
</ResizableBox>Constraint tuples are [width, height] in pixels; defaults are [20, 20] and [Infinity, Infinity].
Allow horizontal resizing onlyresize-one-axis
<ResizableBox
width={280}
height={160}
axis="x"
resizeHandles={['e']}
>
<div>Width only</div>
</ResizableBox>Match the handle to the allowed axis; a north or south handle cannot produce a horizontal delta.
Preserve the starting aspect ratiolock-aspect-ratio
<ResizableBox
width={320}
height={180}
lockAspectRatio
minConstraints={[160, 90]}
maxConstraints={[960, 540]}
>
<div>16:9 preview</div>
</ResizableBox>The ratio comes from the current width and height, so start with the exact proportion you want to preserve.
Enable every edge and cornermultiple-handles
<ResizableBox
width={360}
height={240}
resizeHandles={['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw']}
>
<div>Resize from any side</div>
</ResizableBox>The supplied CSS defines directional classes; custom CSS must position every handle class you enable.
Render a custom handle with its required refcustom-resize-handle
import { forwardRef } from 'react';
const Handle = forwardRef(function Handle({ handleAxis, ...props }, ref) {
return (
<button
ref={ref}
type="button"
aria-label={`Resize from ${handleAxis}`}
className={`resize-handle resize-handle-${handleAxis}`}
{...props}
/>
);
});
<ResizableBox width={300} height={180} handle={<Handle />}>
<div>Custom handle</div>
</ResizableBox>Forward the received ref and pointer props to the DOM node; an ARIA label alone does not add keyboard resizing.
Snap drag deltas to a gridsnap-to-grid
<ResizableBox
width={300}
height={200}
draggableOpts={{ grid: [20, 20] }}
>
<div>20 px increments</div>
</ResizableBox>draggableOpts is passed to react-draggable's DraggableCore; grid values control movement deltas, not CSS layout tracks.
Correct dragging inside a scaled parentcorrect-transform-scale
<div style={{ transform: 'scale(0.75)', transformOrigin: 'top left' }}>
<ResizableBox width={400} height={240} transformScale={0.75}>
<div>Scaled editor canvas</div>
</ResizableBox>
</div>Set transformScale to the parent's actual scale or pointer movement and size deltas will not line up.
Handle resize start, progress, and stoptrack-resize-lifecycle
<ResizableBox
width={320}
height={200}
onResizeStart={() => setDragging(true)}
onResize={(_, { size, handle }) => preview(size, handle)}
onResizeStop={(_, { size }) => {
setDragging(false);
saveSize(size);
}}
>
<div>Persistent panel</div>
</ResizableBox>Persist on stop rather than on every drag event; the callback data also identifies the handle being used.
Type a resize callback in TypeScripttype-resize-callback
import type { ResizeCallbackData } from 'react-resizable';
import type { SyntheticEvent } from 'react';
function onResize(
event: SyntheticEvent,
{ node, size, handle }: ResizeCallbackData,
+) {
console.log(node, size.width, size.height, handle);
}Version 4 exports declarations from the package root; uninstall @types/react-resizable if it remains in the project.
Style the stateful wrapperstyle-resizable-box
<ResizableBox
width={320}
height={200}
className="settings-panel"
style={{ border: '1px solid #bbb', overflow: 'auto' }}
>
<div>Scrollable settings</div>
</ResizableBox>Do not put width or height in style; ResizableBox ignores those two style properties and uses its internal size.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| re-resizable | npm | Use it when you want more built-in size forms and handle styling in a React-specific resizer. |
| react-rnd | npm | Use it when the same React element must be both draggable and resizable with bounded movement. |
| @interactjs/interactjs | npm | Use it for framework-neutral dragging, resizing, gestures, snapping, and inertia beyond a single React component. |