← Back toDrag and drop in React

Create resizable split views

Written byPhuoc Nguyen
Created
31 Oct, 2023
Last updated
10 Nov, 2023
Tags
React splitter
Resizable split views are a type of user interface that lets you divide your screen into two or more sections, each displaying different information or content. You can adjust the size of each section by dragging the divider between them, creating a customizable and flexible layout. This feature is particularly useful in applications where you need to see multiple pieces of information at once, such as text editors, code editors, and email clients.
Many popular applications use resizable split views to enhance their user interface. For instance, Sublime Text lets you split the screen into multiple sections, so you can work on various parts of a project at the same time. Meanwhile, Microsoft Outlook uses split views to show the list of emails on one side and the content of the selected email on another side, enabling you to quickly scan through your inbox while still being able to read individual emails in detail.
Resizable split views can also be found in code editors like Visual Studio Code and Atom, as well as design tools like Sketch and Figma. These applications use split views to display various panels such as file explorer, code editor, debugging console, and design preview all at once.
In this post, we'll learn how to create resizable split views with React. Get ready to take your UI to the next level!

Anatomy of a splitter

As I mentioned earlier, a resizable split view has three main parts: the first panel, the resizer, and the second panel. Let's take a closer look at each one:
  • The first panel: This is the left or top panel, depending on how the split view is set up. It shows the content that will appear in this section of the screen.
  • The resizer: This is the draggable element that separates the two panels. You can click and drag it to adjust the size of each panel as needed.
  • The second panel: This is the right or bottom panel, depending on how the split view is set up. It shows the content that will appear in this section of the screen.
Here's an example of a basic markup for a horizontal splitter, with the left panel on the left and the right panel on the right.
tsx
<div className="splitter">
<div className="splitter__first">Left</div>
<div className="splitter__resizer" />
<div className="splitter__second">Right</div>
</div>
To arrange the splitter layout, we use CSS flexbox. We set the `.splitter` class to `display: flex`, which gives us control over the alignment and positioning of its child elements. The left panel has a fixed width of 50%, while the right panel takes up the remaining space using `flex: 1`.
In addition, you can adjust the width of the left panel programmatically based on the initial value you pass to the component.
To separate the two panels, we create a resizer using a `<div>` element with a height of 100% and a fixed width of 2px, acting as a vertical line. This layout provides an intuitive interface for users to resize the panels according to their needs.
Here's an example of what the styles for a horizontal splitter could look like:
css
.splitter {
align-items: center;
display: flex;
}
.splitter__first {
width: 50%;
height: 100%;
}
.splitter__resizer {
height: 100%;
width: 2px;
}
.splitter__second {
flex: 1;
}
The horizontal splitter looks good without any actual interactions.

Moving the resizer

Let's take a moment to revisit the post where we learned how to create a draggable element. In that post, we used the `useRef()` hook to create a reference to the resizer element and attached it to the element via the `ref` attribute. We also handled the `mousedown` and `touchstart` events to make the resizer draggable on both desktop and touchscreen devices.
Here's a quick snippet of the code to remind you of what we did:
tsx
const resizerRef = React.useRef();

// Render
<div
className="splitter__resizer"
ref={resizerRef}
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
/>
In the past, we positioned draggable elements by setting their position absolutely within their container and updating their position using the `transform` property. However, we don't use that approach for the splitter component. We've eliminated internal states like `dx` and `dy`. Instead, when users start dragging the resizer, we store the current width of the left side and its starting point.
tsx
const handleMouseDown = (e) => {
const startPos = {
x: e.clientX,
y: e.clientY,
};
const currentLeftWidth = firstHalfRef.current.getBoundingClientRect().width;
// ...
}, []);
Next, let's calculate the horizontal and vertical distance that the mouse moves while the user keeps it in motion. Instead of updating the internal states like we did in the previous posts, we can simply update the width of the left side. Don't worry about the `updateWidth` function for now, I'll cover it shortly.
tsx
const handleMouseMove = (e: React.MouseEvent) => {
const dx = e.clientX - startPos.x;
const dy = e.clientY - startPos.y;
updateWidth(currentLeftWidth, dx);
};
The `updateWidth` function takes care of adjusting the width of the left panel when the user drags the resizer. It calculates the current container width and determines the new width of the left panel based on the user's dragging distance. Finally, it updates the style of the left panel element to reflect this new width, using a percentage value.
tsx
const updateWidth = (currentLeftWidth, dx) => {
const container = containerRef.current;
const firstHalfEle = firstHalfRef.current;

const containerWidth = container.getBoundingClientRect().width;
const delta = currentLeftWidth + dx;
const newFirstHalfWidth = (delta * 100) / containerWidth;
firstHalfEle.style.width = `${newFirstHalfWidth}%`;
};
This feature allows users to drag the resizer and watch as the left panel's width changes dynamically and smoothly to fit their actions. Meanwhile, the right panel will automatically adjust its size to fill in any remaining space in the container.
Want to see it in action? Give it a try by dragging the resizer in the middle!

Vertical splitters

Apart from horizontal splitters, we can also create vertical splitters that divide the screen into two sections with a resizable divider in the middle. This feature allows users to adjust the width of each section as per their preference.
Vertical splitters come in handy when you need to view multiple columns of data side by side, such as financial dashboards or news websites. To create a vertical splitter, we can use the same HTML markup and CSS styles as for horizontal splitters, but with some adjustments.
In vertical splitters, the first panel becomes the top panel, and the second panel becomes the bottom panel. The resizer element is still a vertical line between them but with a fixed height instead of width.
tsx
<div className="splitter splitter--vertical">
<div className="splitter__first">Top</div>
<div className="splitter__resizer" />
<div className="splitter__second">Bottom</div>
</div>
To create a vertical splitter layout, we can use CSS flexbox. By default, flex direction is set to row, which gives us a horizontal layout. However, for a vertical splitter, we need to change the flex direction to column. This is done by adding the `flex-direction: column` property to the `.splitter--vertical` class.
To ensure that there is enough space for both panels and that the resizer is visible and easy to grab, we need to set an initial height for the top panel and a fixed height for the resizer element. We can do this using `height: 20%` and `height: 2px`, respectively.
With these CSS styles in place, we can create a fully functional vertical splitter that allows users to adjust the height of each panel as needed.
Here are some additional styles that can be used to enhance the vertical splitter's appearance and functionality:
css
.splitter--vertical {
flex-direction: column;
}
.splitter--vertical .splitter__first {
height: 20%;
width: 100%;
}
.splitter--vertical .splitter__resizer {
cursor: ns-resize;
height: 2px;
width: 100%;
}
To implement the same approach mentioned earlier, we first need to store the starting position of the mouse and the current height of the top element when the user starts dragging the resizer.
tsx
const handleMouseDown = (e) => {
const startPos = {
x: e.clientX,
y: e.clientY,
};
const currentFirstHeight = firstHalfRef.current.getBoundingClientRect().height;
// ...
}, []);
We calculate the horizontal and vertical distance of the mouse movement as long as the user keeps it in motion.
tsx
const handleMouseMove = (e: React.MouseEvent) => {
const dx = e.clientX - startPos.x;
const dy = e.clientY - startPos.y;
updateHeight(currentFirstHeight, dy);
};
The `updateHeight` function is what updates the height of the top panel when you drag the resizer in a vertical splitter. First, it figures out how tall the container is and how much taller the top panel needs to be based on how far you've dragged the resizer. Then, it updates the style of the top panel element to make it that new height, using a percentage value.
We use `container.getBoundingClientRect().height` to calculate the container's height dynamically because its size might change depending on what's inside it or what its parent elements are like.
This makes sure that the top panel's height changes smoothly and in real-time as you drag the resizer around. And the bottom panel automatically adjusts to fill in any extra space left over in the container.
tsx
const updateHeight = (currentFirstHeight, dy) => {
const container = containerRef.current;
const firstHalfEle = firstHalfRef.current;

const containerHeight = container.getBoundingClientRect().height;
const delta = currentFirstHeight + dy;
const newFirstHalfHeight = (delta * 100) / containerHeight;
firstHalfEle.style.height = `${newFirstHalfHeight}%`;
};
Remember to update both the cursor of the resizer and the entire document as users drag the resizer.
tsx
const updateCursor = () => {
const resizerEle = resizerRef.current;
resizerEle.style.cursor = 'ns-resize';
document.body.style.cursor = 'ns-resize';
};
Go ahead and give it a try! Simply drag the resizer in a vertical splitter up or down and see what happens.
It shouldn't be too difficult to merge the implementations into a single component that supports both horizontal and vertical splitters. We'll leave this task of refactoring to you.

Conclusion

Creating a splitter component with React is an excellent way to enhance your web applications' user experience. With just a few lines of code, CSS flexbox, and basic event handling, you can create resizable split panels that let users adjust different sections' size as per their requirements. These split panels are useful for various applications, from web development tools to financial dashboards and news websites.
By following the steps outlined in this post, you can create both horizontal and vertical splitters with ease. Use CSS styles to control the panels' layout and positioning and the resizer element. Moreover, use JavaScript event handlers to make them draggable on different devices.
With these techniques in your toolkit, you can add powerful and flexible UI components to your web projects that deliver an outstanding user experience.

See also

If you found this post helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

Questions? 🙋

Do you have any questions about front-end development? If so, feel free to create a new issue on GitHub using the button below. I'm happy to help with any topic you'd like to learn more about, even beyond what's covered in this post.
While I have a long list of upcoming topics, I'm always eager to prioritize your questions and ideas for future content. Let's learn and grow together! Sharing knowledge is the best way to elevate ourselves 🥷.
Ask me questions

Recent posts ⚡

Newsletter 🔔

If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn