How to build resizable panels in React with Split.js

By Hunter Becton on September 9, 2021

I'm working on a new project called Mattermix that enables users to create graphic design assets from HTML and CSS using a browser-based editor and API.

The browser-based editor needs enough space to write HTML and CSS, create data form fields, and see the rendered code. That's a lot to fit on one screen, but I knew CodePen was already solving a similar design challenge with resizable split panels in their online code editor.

The resizable panels are a simple concept that allows users to create more space for themselves by increasing or decreasing the size of panels using a draggable gutter. Now that I knew what I wanted to achieve, I searched for a solution, which led me to Split.js.

Split.js is a small library that requires no extra dependencies and uses pure CSS for resizing. There's the original Split.js library that works with float and flex layouts, and there's also a Split Grid library that works for grid layouts. For React developers, there are wrapper components at React Split and React Split Grid.

Watch the lesson

Project repo

The code for this project is open-source and available on GitHub. I use Gumroad for those that want to be generous and donate, but no purchase is required. 😊

Grab the code.

Common starting point

In the GitHub repository, you will see a branch called start-here. This is the branch you’ll want to clone if you want to follow along from the same starting point, but it’s unnecessary.

In this branch, I’ve created a Next.js project with Tailwind CSS. I also made some starter components like a shared Layout component and a Nav component that uses a free navigation bar from Tailwind UI.

Install and set React Split styles

In your project’s terminal, run the command npm i react-split to install React Split. Once React Split is installed, add the CSS styles for the gutter. You can read more about that on the Split.js documentation, but for now, write the following code in your app’s CSS:

.gutter {
  background-color: #e5e7eb;
  background-repeat: no-repeat;
  background-position: 50%;
}

.gutter.gutter-horizontal {
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==');
  cursor: col-resize;
}

.gutter.gutter-vertical {
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=');
  cursor: row-resize;
}

Example One

The first example will be a CodePen clone: a top and bottom panel with the top panel having three separate panels nested inside.

The way React Split works is relatively straightforward. Panels are created based on the direct children of the Split component. For example, if you wanted three panels, you would nest three dividers inside one Split component.

With that in mind, let’s go ahead and create a new component and create the top and bottom split panels:

import Split from 'react-split'

export const Editor1 = () => {
  return (
    <Split direction="vertical" style={{ height: `calc(100vh - 4rem)` }}>
      <div className="bg-gray-base"></div>
      <div className="bg-gray-mid"></div>
    </Split>
  )
}

Notice how the Split component has a prop called direction. By default, the Split component will set the direction prop to horizontal, which we want on our nested panels, but not for our top and bottom panels.

You also will notice that inline styles are passed in that set the height of the Split component. These styles are necessary because the Split component needs to know how much space to take up, which is the entire screen minus the height of the navigation.

The top panel is a simple div right now. To add the three nested panels, you’ll want to make the top panel a new Split component with three divs inside as direct children. For example, your code should now look like this:

import Split from 'react-split'

export const Editor1 = () => {
  return (
    <Split direction="vertical" style={{ height: `calc(100vh - 4rem)` }}>
      <Split className="flex">
        <div className="bg-gray-light"></div>
        <div className="bg-gray-light"></div>
        <div className="bg-gray-light"></div>
      </Split>
      <div className="bg-gray-mid"></div>
    </Split>
  )
}

Notice that the first Spit component’s first child is now another Split component, which will turn the top panel into panels. Also, notice that the new Split component has the className of flex applied. This utility class from Tailwind CSS will add default flexbox styles, which will style the Split component to take up the remaining space.

To create the three panels, you’ll create three divs inside the nested Split component. By default, the Split component will set the size of the panels to equal measures, but we’ll cover how to change those defaults in example two. These divs also have the bg-gray-light utility class applied to give the color of their background.

Example two

As mentioned in the first example, the Split component has various default props for initial size, minimum size, maximum size, and more. You can read more about all the available props in the React Split documentation. We will clone the first example but pass in some new props to set initial and minimum sizes in this example. With that, your component should look like this.

import Split from 'react-split'

export const Editor2 = () => {
  return (
    <Split
      direction="vertical"
      sizes={[30, 70]}
      style={{ height: `calc(100vh - 4rem)` }}
    >
      <Split className="flex" sizes={[20, 35, 45]} minSize={[10, 40, 90]}>
        <div className="bg-gray-light"></div>
        <div className="bg-gray-light"></div>
        <div className="bg-gray-light"></div>
      </Split>
      <div className="bg-gray-mid"></div>
    </Split>
  )
}

The first Split component has one new sizes prop. This prop can be a single value, which would set the same size to all the panels, or you can pass in an array and set the dimensions of the panels individually. This number represents a percentage, so it must add up to 100% if you want the panels to take up the entire space of the Split component.

The nested Split component also has the sizes prop and the minSize prop. This prop is similar to the sizes prop, but it sets the minimum size a panel can be. However, these values represent pixels, so they don’t have to add up to 100%.

Example three

The last example is a clone of the first example, but with the functionality to minimize a panel on a button click. The original Split.js library has methods to do this functionality, but the React Split controls this functionality with props. You can read more about using the functionality with React Split in the Migrating from Spit.js section of the documentation.

To start, let’s create the button component, which uses icons from the React Icons library. Your code should look like this:

import { BiMinus } from 'react-icons/bi'

export const CollapseButton = ({ onClick }) => {
  return (
    <button
      type="button"
      onClick={onClick}
      className="bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500 inline-flex items-center rounded-full border border-transparent p-1 text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2"
    >
      <BiMinus className="h-5 w-5" aria-hidden="true" />
    </button>
  )
}

Now with the CollapseButton component completed, you can use it in the following example:

import { useState } from 'react'
import Split from 'react-split'
import { CollapseButton } from '/components'

export const Editor3 = () => {
  const [collapsedIndex, setCollapsedIndex] = useState(null)

  return (
    <Split direction="vertical" style={{ height: `calc(100vh - 4rem)` }}>
      <Split className="flex" collapsed={collapsedIndex}>
        <Options>
          <CollapseButton onClick={() => setCollapsedIndex(0)} />
        </Options>
        <Options>
          <CollapseButton onClick={() => setCollapsedIndex(1)} />
        </Options>
        <Options>
          <CollapseButton onClick={() => setCollapsedIndex(2)} />
        </Options>
      </Split>
      <div className="bg-gray-mid"></div>
    </Split>
  )
}

const Options = ({ children }) => {
  return (
    <div className="relative overflow-hidden bg-gray-light">
      <div className="absolute top-2 left-2 flex flex-col space-y-2">
        {children}
      </div>
    </div>
  )
}

This example relies on the useState hook from React to manage the collapsed panel. To change the state, you will use the setCollapsedIndex in the onClick handler, passing the panel’s index. Now, with just a click of a button, your users can minimize a panel.

Conclusion

React Split is an excellent component wrapper for Split.js and makes it easy to create resizable panels in React. With a few lines of code, you can get started or pass in some extra props and functionality to customize the split panels for your project.