react-resizable, react-split-pane, and react-splitter-layout are three distinct approaches to building dynamic, user-resizable interfaces in React. react-resizable is a low-level primitive that adds resize handles to any single element, giving you full control over the logic but requiring you to build the layout structure yourself. react-split-pane is a higher-level component that manages two panes and a divider between them, handling the math and state internally for quick split-view implementations. react-splitter-layout offers a similar split-view abstraction but focuses on percentage-based sizing and a simpler API for standard horizontal or vertical splits, often used in dashboard layouts.
Building interfaces that allow users to resize panels—like IDEs, dashboards, or data grids—is a common but tricky challenge in frontend development. You need to handle mouse events, calculate new dimensions, prevent text selection during drags, and manage state without causing performance jitters. The React ecosystem offers three main contenders for this job: react-resizable, react-split-pane, and react-splitter-layout. While they all solve the problem of "dragging a line to change size," they operate at different levels of abstraction and impose different architectural constraints.
Let's break down how they work, where they shine, and how to implement them in real-world scenarios.
The most critical difference between these libraries is how much of the layout logic they handle for you.
react-resizable is a primitive. It does not create a split view for you. Instead, it wraps a single element and adds a "handle" to it. You are responsible for creating the container, managing the state of the width/height, and deciding how the sibling elements react when one resizes. This gives you maximum flexibility but requires more boilerplate code.
// react-resizable: You manage the state and the layout container
import { Resizable } from 'react-resizable';
function CustomLayout() {
const [width, setWidth] = React.useState(200);
return (
<div style={{ display: 'flex' }}>
<Resizable
width={width}
height={300}
onResize={(e, { size }) => setWidth(size.width)}
handle={<span className="custom-handle" />}
>
<div style={{ width, height: 300, background: '#eee' }}>Panel 1</div>
</Resizable>
<div style={{ flex: 1, background: '#fff' }}>Panel 2 (Flex grows)</div>
</div>
);
}
react-split-pane is a complete solution. It encapsulates the entire split logic. You pass two children (the left/top pane and the right/bottom pane), and it renders them with a divider in between. It manages the drag state, calculates the new sizes, and applies the styles internally. You just declare the structure.
// react-split-pane: The library manages the split state internally
import SplitPane from 'react-split-pane';
function EditorLayout() {
return (
<SplitPane split="vertical" defaultSize={200} minSize={100}>
<div style={{ background: '#eee' }}>Sidebar</div>
<div style={{ background: '#fff' }}>Main Content</div>
</SplitPane>
);
}
react-splitter-layout sits in the middle but leans towards a declarative, percentage-based approach. Like react-split-pane, it wraps two children, but it focuses heavily on percentage sizes rather than fixed pixels. It simplifies the API by removing many of the complex configuration options found in other libraries, aiming for a "just work" experience for standard splits.
// react-splitter-layout: Focused on percentage-based splits
import SplitterLayout from 'react-splitter-layout';
function Dashboard() {
return (
<SplitterLayout percentage={true} initialPrimarySize={30}>
<div style={{ background: '#eee' }}>Navigation (30%)</div>
<div style={{ background: '#fff' }}>Content (70%)</div>
</SplitterLayout>
);
}
How the libraries calculate size dictates where they fit in your responsive strategy.
react-resizable and react-split-pane primarily operate using pixels. You define a minSize, a defaultSize, and the component reports back changes in pixels. This is excellent for fixed-width sidebars (e.g., a file tree that should always be at least 200px) but can require extra math if you want the layout to be fully fluid on window resize.
In react-split-pane, you explicitly set the size in pixels:
// react-split-pane: Explicit pixel control
<SplitPane
split="horizontal"
defaultSize={400}
minSize={200}
maxSize={600}
>
<Header />
<Body />
</SplitPane>
react-splitter-layout distinguishes itself by prioritizing percentages. While it can handle pixels, its core API encourages defining the primary pane as a percentage of the total container. This makes it naturally more responsive for full-screen dashboard layouts where you want the sidebar to always take up 20% of the screen, regardless of device width.
// react-splitter-layout: Native percentage support
<SplitterLayout
percentage={true}
initialPrimarySize={25} // 25% of the container
>
<Aside />
<Main />
</SplitterLayout>
When your design team demands a specific look for the resize handle or unique drag behaviors, the libraries offer different levels of access.
react-resizable offers the highest degree of customization because you render the handle yourself. You can pass any React node as the handle prop. This is crucial if your design requires a custom icon, a specific hover effect, or a handle that only appears on hover.
// react-resizable: Full control over the handle markup
<Resizable
width={width}
height={height}
onResize={onResize}
handle={
<div className="my-custom-handle">
<IconDragVertical />
</div>
}
>
<Content />
</Resizable>
react-split-pane allows customization via the paneStyle, pane2Style, and resizerStyle props, or by passing a custom component to the resizerComponent prop. However, you are still working within the constraints of the library's internal flexbox or absolute positioning logic. It is flexible, but you might fight the library if you need a non-standard layout structure.
// react-split-pane: Styling via props or custom resizer component
<SplitPane
split="vertical"
resizerStyle={{ backgroundColor: '#007bff', width: '5px' }}
resizerComponent={props => <div {...props} className="blue-resizer" />}
>
<LeftPane />
<RightPane />
>
</SplitPane>
react-splitter-layout provides the least amount of configuration options. It relies on CSS classes for styling the splitter bar. While you can override these styles in your CSS file, you cannot easily inject custom React components into the resizer slot without forking the library or using advanced DOM manipulation. It trades flexibility for simplicity.
// react-splitter-layout: Styling via CSS classes only
// In your CSS file:
// .layout-splitter { background: #333; }
// .layout-splitter-horizontal { height: 5px; }
<SplitterLayout customSplitter={<div className="custom-bar" />}>
{/* Note: Actual API uses CSS classes for styling, customSplitter prop varies by version */}
<Panel1 />
<Panel2 />
</SplitterLayout>
Before making a final decision, you must consider the maintenance status of these packages.
react-split-pane has historically been the most popular choice, but it has faced significant periods of inactivity and maintenance issues. There have been multiple forks created by the community to fix bugs or add TypeScript support because the main repository was stagnant for long periods. If you choose this library, you must verify the specific fork or npm package version you are installing is actively maintained, or be prepared to patch it yourself.
react-resizable is generally considered stable and low-maintenance because it does less. It is a "set it and forget it" primitive. Since it doesn't try to solve the whole layout problem, there are fewer edge cases to break, making it a safer long-term bet for critical infrastructure.
react-splitter-layout is lightweight and simple, which usually correlates with fewer breaking changes. However, its smaller community means fewer third-party plugins or examples to rely on if you hit a wall.
Real-world apps often need splits inside of splits (e.g., a vertical split for the sidebar, and a horizontal split for the main content area).
With react-resizable, nesting is straightforward because you control the container. You simply nest Resizable components inside flex containers as deep as you need. The isolation of state makes debugging easier.
// react-resizable: Easy nesting via composition
<div style={{ display: 'flex', height: '100vh' }}>
<Resizable width={200} onResize={...}><Sidebar /></Resizable>
<div style={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
<Resizable height={300} onResize={...}><Header /></Resizable>
<div style={{ flex: 1 }}><MainContent /></div>
</div>
</div>
With react-split-pane, nesting works but can become verbose. You have to wrap the entire content of one pane in another SplitPane. This can lead to "prop drilling" if you need to control sizes from a parent component.
// react-split-pane: Nested splits require wrapping children
<SplitPane split="vertical">
<Sidebar />
<SplitPane split="horizontal">
<Header />
<MainContent />
</SplitPane>
</SplitPane>
react-splitter-layout handles nesting similarly to react-split-pane, by composing components. Its percentage-based logic can sometimes make calculating nested sizes tricky if you mix percentages and fixed pixels across levels.
// react-splitter-layout: Nested composition
<SplitterLayout percentage={true} initialPrimarySize={20}>
<Sidebar />
<SplitterLayout percentage={false} initialPrimarySize={400}>
<TopPanel />
<BottomPanel />
</SplitterLayout>
</SplitterLayout>
| Feature | react-resizable | react-split-pane | react-splitter-layout |
|---|---|---|---|
| Abstraction | Low (Primitive) | High (Complete Widget) | Medium (Simple Widget) |
| Primary Unit | Pixels | Pixels | Percentages (Configurable) |
| Custom Handles | ✅ Full React Node Control | ⚠️ Via Props/Components | ❌ Limited to CSS |
| State Management | User Controlled | Internal | Internal |
| Nesting | Easy (Manual Layout) | Moderate (Component Tree) | Moderate (Component Tree) |
| Best For | Custom Grids, Complex Apps | Quick Split Views, Editors | Dashboards, % Based Layouts |
If you are building a complex application like a video editor, a sophisticated IDE, or a dashboard where the layout rules are unique (e.g., "this panel can only grow if that one shrinks, but never below 10%"), choose react-resizable. The initial effort to write the layout logic pays off in long-term maintainability and flexibility. You own the code, so you aren't blocked by library limitations.
If you need to ship a standard split view quickly (like a settings page with a sidebar) and want to avoid writing drag-and-drop math, choose react-split-pane, but verify the maintenance status of the specific version you install. Consider using a well-maintained fork if the original is stale.
If your design system relies heavily on responsive percentages and you want the simplest possible API for standard horizontal/vertical splits, react-splitter-layout is a solid, lightweight choice that gets the job done with minimal configuration.
Choose react-resizable when you need to build a custom grid system, a complex multi-pane layout, or when the split logic requires non-standard behavior that pre-built splitters cannot handle. It is the best fit if you want to own the state management and layout algorithm, using the library only for the drag-and-drop mechanics of the resize handle.
Choose react-split-pane if you need a quick, robust solution for a classic two-pane view (like a code editor with a sidebar) and want the library to handle the pixel-perfect calculations and event listeners for you. It is ideal for prototypes or applications where standard split behavior is sufficient and you prefer a component-based approach over manual math.
Choose react-splitter-layout when your layout requirements are strictly horizontal or vertical splits defined by percentages rather than fixed pixels, and you prefer a minimal API with fewer props to manage. It is a strong candidate for admin dashboards where simplicity and percentage-based responsiveness are more important than complex nested splitting capabilities.
A simple widget that can be resized via one or more handles.
You can either use the <Resizable> element directly, or use the much simpler <ResizableBox> element.
See the example and associated code in ExampleLayout and ResizableBox for more details.
$ npm install --save react-resizable
You must include the associated styles in your application, otherwise the resize handles will not be visible and will not work properly.
// In your JS/TS entry point:
import 'react-resizable/css/styles.css';
Or import it in your CSS:
@import 'react-resizable/css/styles.css';
If you're using a bundler that doesn't support CSS imports, you can find the styles at node_modules/react-resizable/css/styles.css and include them manually.
As of 4.0.0, the library is authored in TypeScript and ships bundled type
declarations in build/*.d.ts. You do not need to install
@types/react-resizable; if you previously installed it, remove it so the
bundled types take precedence:
npm uninstall @types/react-resizable
# or
yarn remove @types/react-resizable
Public types are re-exported from the package root:
import {
Resizable,
ResizableBox,
// types
type ResizeCallbackData,
type ResizeHandleAxis,
type Axis,
type Props as ResizableProps,
} from 'react-resizable';
Flow is no longer supported as of 4.0.0. Earlier versions shipped
*.js.flow sidecar files generated from the Flow-annotated source; those
have been removed.
If you still need Flow types, you can vendor the last Flow-annotated source
locally from the 3.2.0 tag.
They will not be updated to reflect changes landing after 4.0.0. The
official recommendation is to migrate to TypeScript.
| Version | React Version | Types |
|---|---|---|
| 4.x | >= 16.3 | TypeScript (bundled) |
| 3.x | >= 16.3 | Flow (*.js.flow) |
| 2.x | Skipped | — |
| 1.x | 14 - 17 | Flow |
This package has two major exports:
<Resizable>: A raw component that does not have state. Use as a building block for larger components, by listening to its callbacks and setting its props.<ResizableBox>: A simple <div {...props} /> element that manages basic state. Convenient for simple use-cases.<Resizable>import { Resizable } from 'react-resizable';
import 'react-resizable/css/styles.css';
class Example extends React.Component {
state = {
width: 200,
height: 200,
};
onResize = (event, {node, size, handle}) => {
this.setState({width: size.width, height: size.height});
};
render() {
return (
<Resizable
height={this.state.height}
width={this.state.width}
onResize={this.onResize}
>
<div
className="box"
style={{width: this.state.width + 'px', height: this.state.height + 'px'}}
>
<span>Contents</span>
</div>
</Resizable>
);
}
}
<ResizableBox>import { ResizableBox } from 'react-resizable';
import 'react-resizable/css/styles.css';
class Example extends React.Component {
render() {
return (
<ResizableBox
width={200}
height={200}
draggableOpts={{grid: [25, 25]}}
minConstraints={[100, 100]}
maxConstraints={[300, 300]}
>
<span>Contents</span>
</ResizableBox>
);
}
}
These props apply to both <Resizable> and <ResizableBox>. Unknown props that are not in the list below will be passed to the child component.
type ResizeCallbackData = {
node: HTMLElement;
size: {width: number; height: number};
handle: ResizeHandleAxis;
};
type ResizeHandleAxis = 's' | 'w' | 'e' | 'n' | 'sw' | 'nw' | 'se' | 'ne';
type ResizableProps = {
children: React.ReactElement<any>;
width: number;
height: number;
// Either a ReactElement to be used as handle, or a function
// returning an element that is fed the handle's location as its first argument.
handle?:
| React.ReactElement<any>
| ((resizeHandle: ResizeHandleAxis, ref: React.RefObject<HTMLElement>) => React.ReactElement<any>);
// If you change this, be sure to update your css. Default: [20, 20].
handleSize?: [number, number];
lockAspectRatio?: boolean; // default: false
axis?: 'both' | 'x' | 'y' | 'none'; // default: 'both'
minConstraints?: [number, number]; // default: [20, 20]
maxConstraints?: [number, number]; // default: [Infinity, Infinity]
onResizeStop?: (e: React.SyntheticEvent, data: ResizeCallbackData) => any;
onResizeStart?: (e: React.SyntheticEvent, data: ResizeCallbackData) => any;
onResize?: (e: React.SyntheticEvent, data: ResizeCallbackData) => any;
// Forwarded to react-draggable's <DraggableCore>.
draggableOpts?: Partial<React.ComponentProps<typeof import('react-draggable').DraggableCore>>;
resizeHandles?: ResizeHandleAxis[]; // default: ['se']
// If `transform: scale(n)` is set on the parent, this should be set to `n`.
transformScale?: number; // default: 1
};
The following props can also be used on <ResizableBox>:
{
style?: React.CSSProperties; // styles the returned <div />
}
If a width or height is passed to <ResizableBox>'s style prop, it will be ignored as it is required for internal function.
You can pass options directly to the underlying DraggableCore instance by using the prop draggableOpts. See the demo for more on this.
If you override the resize handle, we expect that any ref passed to your new handle will represent the underlying DOM element.
This is required, as react-resizable must be able to access the underlying DOM node to attach handlers and measure position deltas.
There are a few ways to do this:
This requires no special treatment.
<Resizable handle={<div className="foo" />} />
You must forward the ref and props to the underlying DOM element.
class MyHandleComponent extends React.Component {
render() {
const {handleAxis, innerRef, ...props} = this.props;
return <div ref={innerRef} className={`foo handle-${handleAxis}`} {...props} />
}
}
const MyHandle = React.forwardRef((props, ref) => <MyHandleComponent innerRef={ref} {...props} />);
<Resizable handle={<MyHandle />} />
const MyHandle = React.forwardRef((props, ref) => {
const {handleAxis, ...restProps} = props;
return <div ref={ref} className={`foo handle-${handleAxis}`} {...restProps} />;
});
<Resizable handle={<MyHandle />} />
You can define a function as a handle, which will simply receive an axis (see above ResizeHandleAxis type) and ref. This may be more clear to read, depending on your coding style.
const MyHandle = (props) => {
return <div ref={props.innerRef} className="foo" {...props} />;
};
<Resizable handle={(handleAxis, ref) => <MyHandle innerRef={ref} className={`foo handle-${handleAxis}`} />} />
MIT