Skip to content

Pro pre-sale is open. Installation opens January 2027. See Pro

Components

ResizablePanels

Panels side by side or stacked, with a line between two panels that the user drags or moves with the arrow keys to give one more room, such as a list next to its detail or a query over its results. Sizes are percents of the group.

  • npm: 1 direct, 2 total
  • registry: 2 direct, 2 total

Preview

Inbox

Message

Props

  • orientation
  • label
  • withHandle
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable-panels"

<ResizablePanelGroup className="h-64 w-full rounded-container border border-border">
  <ResizablePanel defaultSize={35} minSize={20} className="p-md">
    <p className="text-sm text-text-secondary">Inbox</p>
  </ResizablePanel>
  <ResizableHandle label="Resize inbox" />
  <ResizablePanel className="p-md">
    <p className="text-sm text-text-secondary">Message</p>
  </ResizablePanel>
</ResizablePanelGroup>

Usage

When to use

  • Let someone give more room to one of two or three panels, such as a list next to its detail.
  • Split an editor and its output, or a table and a preview, at a size the user picks.

When not to use

  • The app's main navigation column. Use Sidebar.
  • Content that overflows one box. Use ScrollArea.
  • Switch between views in one place. Use Tabs.

Variants

orientation

Set on ResizablePanelGroup. horizontal puts the panels side by side, vertical stacks them.

  • Inbox

    Message

    horizontaldefault

  • Query

    Output

    vertical

import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable-panels"

<ResizablePanelGroup className="h-64 w-full rounded-container border border-border">
  <ResizablePanel defaultSize={35} minSize={20} className="p-md">
    <p className="text-sm text-text-secondary">Inbox</p>
  </ResizablePanel>
  <ResizableHandle label="Resize inbox" />
  <ResizablePanel className="p-md">
    <p className="text-sm text-text-secondary">Message</p>
  </ResizablePanel>
</ResizablePanelGroup>
<ResizablePanelGroup orientation="vertical" className="h-64 w-full rounded-container border border-border">
  <ResizablePanel defaultSize={35} minSize={20} className="p-md">
    <p className="text-sm text-text-secondary">Query</p>
  </ResizablePanel>
  <ResizableHandle label="Resize inbox" />
  <ResizablePanel className="p-md">
    <p className="text-sm text-text-secondary">Output</p>
  </ResizablePanel>
</ResizablePanelGroup>

Examples

List and detail

Inbox

Message

import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable-panels"

<ResizablePanelGroup className="h-64 w-full rounded-container border border-border">
  <ResizablePanel defaultSize={35} minSize={20} className="p-md">
    <p className="text-sm text-text-secondary">Inbox</p>
  </ResizablePanel>
  <ResizableHandle label="Resize inbox" />
  <ResizablePanel className="p-md">
    <p className="text-sm text-text-secondary">Message</p>
  </ResizablePanel>
</ResizablePanelGroup>

Stacked, with a grip

A vertical group puts the panels one over the other; the grip shows where to grab.

Query

Output

import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable-panels"

<ResizablePanelGroup orientation="vertical" className="h-72 w-full rounded-container border border-border">
  <ResizablePanel defaultSize={60} className="p-md">
    <p className="text-sm text-text-secondary">Query</p>
  </ResizablePanel>
  <ResizableHandle label="Resize output" withHandle />
  <ResizablePanel className="p-md">
    <p className="text-sm text-text-secondary">Output</p>
  </ResizablePanel>
</ResizablePanelGroup>

Three panels

Folders

Files

Preview

import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable-panels"

<ResizablePanelGroup className="h-64 w-full rounded-container border border-border">
  <ResizablePanel defaultSize={25} minSize={15} className="p-md">
    <p className="text-sm text-text-secondary">Folders</p>
  </ResizablePanel>
  <ResizableHandle label="Resize folders" />
  <ResizablePanel className="p-md">
    <p className="text-sm text-text-secondary">Files</p>
  </ResizablePanel>
  <ResizableHandle label="Resize files" />
  <ResizablePanel defaultSize={30} minSize={20} className="p-md">
    <p className="text-sm text-text-secondary">Preview</p>
  </ResizablePanel>
</ResizablePanelGroup>

Do and don't

Do

  • Set minSize so no panel shrinks below what its content needs.
  • Save the layout from onLayout and pass it back as defaultSize on the next visit.
  • Below md, stack the content or show one panel at a time instead.

Don't

  • Do not use for the app's main navigation column. Use Sidebar, which collapses.
  • Do not put more than three panels in one group.
  • Do not put a handle at the start or the end; a handle sits between two panels.

Props

NameTypeValuesDefaultDescription
orientationenumhorizontal | verticalhorizontalSet on ResizablePanelGroup. horizontal puts the panels side by side, vertical stacks them.
onLayout(sizes: number[]) => void--Set on ResizablePanelGroup. Called with every panel's size in percent after a drag or a key press, to save the layout.
keyboardStepnumber-5Set on ResizablePanelGroup. Percent a handle moves for each arrow key press.
defaultSizenumber--Set on ResizablePanel. Starting size in percent. Panels without one share what is left.
minSizenumber-10Set on ResizablePanel. Smallest size in percent.
maxSizenumber-90Set on ResizablePanel. Largest size in percent.
label requiredstring--Set on ResizableHandle. Names the handle, such as Resize sidebar.
withHandleboolean-falseSet on ResizableHandle. Shows a small grip tile on the line, for a handle that is hard to find.

Slots

NameRequiredDescription
panelsnoChildren of ResizablePanelGroup: ResizablePanel and ResizableHandle, a handle between each two panels, as direct children.

Anatomy

PartDescription
rootThe group: a flex row or column that fills its container. Give it a height when it is horizontal.
panelOne panel. It grows by its share and scrolls on its own when the content is larger.
handleThe 1px line between two panels, focusable and draggable.
hitAreaThe transparent ::before across the line, size-target-min wide.
gripThe optional small tile on the line that shows where to grab.
iconThe grip icon in the tile, 16px.

Accessibility

Role: separator

KeysAction
TabMoves focus to the next handle.
ArrowLeft / ArrowRightIn a horizontal group, makes the panel before the handle smaller or larger by keyboardStep percent.
ArrowUp / ArrowDownIn a vertical group, makes the panel above the handle smaller or larger by keyboardStep percent.
Home / EndMakes the panel before the handle as small or as large as its limits allow.
  • Each handle is a focusable separator named by label, such as EN `Resize sidebar`. It controls the panel before it (aria-controls).
  • aria-valuenow is the size of the panel before the handle in percent; aria-valuemin and aria-valuemax are its limits, given the next panel's limits too.
  • A horizontal group has vertical separators (aria-orientation="vertical") and the reverse.
  • In a right-to-left page the arrow keys and the drag follow the reading direction.
  • The 1px line has a hit area of size-target-min across it, so it is easy to grab with a finger.

Install

npx werewolf-ui add resizable-panels

Run npx werewolf-ui init once in your app before adding components. Init sets up tokens, their CSS import and agent rules. Add installs this component and its dependencies while preserving your theme. See Installation for requirements and the alternative registry setup.