Resizable

Panels side by side or stacked, with a line between them that people drag to give one more room.

Files

Drag the line

Editor

Or use the arrow keys

Terminal

Panels can be nested

import {
  ResizableHandle,
  ResizablePanel,

When to use it

Use resizable panels for a workspace where people decide how much room each part gets: a file list beside an editor, a message list beside a conversation, a map above a table. The layout is theirs to adjust, so it suits tools that are used for a long time.

Do not use it on a page that people read once. A fixed layout is simpler and has fewer ways to go wrong. For a panel that opens and closes without a size, use a Drawer or a Collapsible.

It is built on react-resizable-panels.

Import

import {
  ResizablePanelGroup,
  ResizablePanel,
  ResizableHandle,
  usePanelRef,
} from "@hel/ui/resizable"

Anatomy

A ResizablePanelGroup holds the ResizablePanels, with a ResizableHandle between each pair. The group fills the width and height of its parent, so the parent needs a height of its own, such as h-72. A height set on the group itself is ignored, because the library sets it inline. Put a group inside a panel to nest them, as the preview does with the editor and the terminal.

<Stack className="h-72">
  <ResizablePanelGroup orientation="horizontal">
    <ResizablePanel defaultSize="30%" minSize="15%">...</ResizablePanel>
    <ResizableHandle withHandle />
    <ResizablePanel>...</ResizablePanel>
  </ResizablePanelGroup>
</Stack>

Sizes are percentages when they are strings ("30%"), and pixels when they are numbers (240). Any CSS unit as a string also works, such as "16rem". Give each panel a minSize, so people cannot drag one down to nothing by accident.

A handle is a separator that people can also reach with the keyboard. Focus it, and the arrow keys move it, Home and End jump it to the smallest and largest size. Under a right-to-left direction, the horizontal panels mirror, so the first panel is on the right.

Examples

Collapsible panel

Mark a panel collapsible with a collapsedSize, and dragging past its minimum snaps it shut. Get a handle on it with usePanelRef to open and close it from a button, and use onResize to keep the label of the button right.

Conversations

Drag the line all the way left to collapse this panel.

Pick a conversation to read it here.

import * as React from "react"
import { Button } from "@hel/ui/button"
import {

Remembering the layout

To keep the sizes between visits, use useDefaultLayout from react-resizable-panels with the id of the group, and pass what it returns to the group as defaultLayout and onLayoutChanged. It can save to localStorage or to a cookie, which is better if the page is rendered on the server, because the first render already has the right sizes.

Props

ResizablePanelGroup

PropTypeDefault
orientation"horizontal" | "vertical""horizontal"
borderedbooleanfalse
defaultLayoutthe sizes by panel idnone
onLayoutChanged(layout) => voidnone
disabledbooleanfalse

bordered draws a rounded line around the group, for a layout that sits inside a page.

ResizablePanel

PropTypeDefault
defaultSizenumber | stringan equal share
minSize / maxSizenumber | string0 / 100%
collapsiblebooleanfalse
collapsedSizenumber | string0
onResize(size) => voidnone
panelReffrom usePanelRefnone
paddinga spacing step"none"

The content of a panel is a column, and it scrolls when it is taller than the panel. The handle from usePanelRef has collapse(), expand(), isCollapsed(), resize(size) and getSize().

ResizableHandle

PropTypeDefault
withHandlebooleanfalse
disabledbooleanfalse

withHandle draws a small grip on the line, so it is easy to see. The label that screen readers read is "Resize", and you can translate it with messages in the config.