react-resizable vs react-split-pane vs react-splitter-layout
Architecting Resizable Layouts: Choosing the Right React Component for Panes and Handles
react-resizablereact-split-panereact-splitter-layoutSimilar Packages:

Architecting Resizable Layouts: Choosing the Right React Component for Panes and Handles

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
react-resizable4,643,8402,58344.3 kB123 months agoMIT
react-split-pane326,9503,391104 kB127 months agoMIT
react-splitter-layout21,149431-387 years agoMIT

Architecting Resizable Layouts: A Deep Dive into react-resizable, react-split-pane, and react-splitter-layout

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.

🏗️ Level of Abstraction: Primitive vs. Complete Solution

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>
  );
}

📐 Sizing Logic: Pixels vs. Percentages

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>

🎨 Customization and Styling Control

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>

⚠️ Maintenance and Architecture Warning

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.

🧩 Handling Nested Splits

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>

📊 Summary Comparison

Featurereact-resizablereact-split-panereact-splitter-layout
AbstractionLow (Primitive)High (Complete Widget)Medium (Simple Widget)
Primary UnitPixelsPixelsPercentages (Configurable)
Custom Handles✅ Full React Node Control⚠️ Via Props/Components❌ Limited to CSS
State ManagementUser ControlledInternalInternal
NestingEasy (Manual Layout)Moderate (Component Tree)Moderate (Component Tree)
Best ForCustom Grids, Complex AppsQuick Split Views, EditorsDashboards, % Based Layouts

💡 Final Architectural Recommendation

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.

How to Choose: react-resizable vs react-split-pane vs react-splitter-layout

  • react-resizable:

    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.

  • react-split-pane:

    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.

  • react-splitter-layout:

    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.

README for react-resizable

React-Resizable

npm version npm downloads Build Status

View the Demo

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.

Table of Contents

Installation

$ npm install --save react-resizable

Extracting Styles

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.

TypeScript

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

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.

Compatibility

VersionReact VersionTypes
4.x>= 16.3TypeScript (bundled)
3.x>= 16.3Flow (*.js.flow)
2.xSkipped
1.x14 - 17Flow

Usage

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>
    );
  }
}

Props

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.

Resize Handle

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:

Native DOM Element

This requires no special treatment.

<Resizable handle={<div className="foo" />} />

Custom React Component

You must forward the ref and props to the underlying DOM element.

Class Components

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 />} />

Functional Components

const MyHandle = React.forwardRef((props, ref) => {
  const {handleAxis, ...restProps} = props;
  return <div ref={ref} className={`foo handle-${handleAxis}`} {...restProps} />;
});

<Resizable handle={<MyHandle />} />

Custom Function

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}`} />} />

License

MIT