Panels split by draggable hairline handles, side by side or stacked, with keyboard resizing and minimum sizes.
ResizablePanelGroup holds ResizablePanel and ResizableHandle children, alternating. Sizes are percentages applied as flex-grow proportions, so handles keep their pixel width and nothing overflows. Each panel can set defaultSize and minSize; onLayout reports every size after a change, for saving the split.
Drag a handle with the pointer, or focus it and use the arrow keys (5%, Shift for 10%) and Home or End. withGrip draws a small grip on the rule. framed (default) puts the group on a glass panel.
Installation
npx shadcn@latest add KezLahd/rime/resizableAlso installs: base.
Usage
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui";
<ResizablePanelGroup height={320}>
<ResizablePanel defaultSize={30} minSize={20}>Projects</ResizablePanel>
<ResizableHandle withGrip />
<ResizablePanel>Details</ResizablePanel>
</ResizablePanelGroup>Examples
Three panels
Vertical
API
Generated from the component's TypeScript types and JSDoc (scripts/gen-ui-registry.mjs), so it always matches the source.
ResizablePanelGroup
| Prop | Type | Default | Description |
|---|---|---|---|
| direction | "horizontal" | "vertical" | "horizontal" | horizontal = panels side by side (drag left and right). Default horizontal. |
| childrenRequired | ReactNode | ResizablePanel and ResizableHandle children, alternating. | |
| onLayout | (sizes: number[]) => void | Called with every panel's size, in percent, after each change. | |
| height | number | string | Group height for vertical groups (or any fixed frame), e.g. 320. | |
| framed | boolean | true | Glass frame around the group with hairline handles. Default true. |
| className | string |
ResizablePanel
| Prop | Type | Default | Description |
|---|---|---|---|
| defaultSize | number | Starting size in percent of the group. Unset panels share what is left. | |
| minSize | number | Smallest size in percent. Default 10. | |
| children | ReactNode | ||
| className | string |
ResizableHandle
| Prop | Type | Default | Description |
|---|---|---|---|
| aria-label | string | Accessible name. Defaults to "Resize". | |
| withGrip | boolean | Shows a small grip on the rule. | |
| className | string |
Theme tokens
Per-component tokens are undeclared by default, so the default shown is the fallback the CSS uses. Set one at :root (or in theme.css) to restyle every instance. Live values are read from this page, so they follow the Rime Studio.
| Token | Default | Live value |
|---|---|---|
| --resizable-min-height | 200px | … |
| --resizable-radius | var(--r-lg) | … |
System tokens it reads
| Token | Default | Live value |
|---|---|---|
| --blur-panel-light | tokens.css | … |
| --border-w | tokens.css | … |
| --brand | tokens.css | … |
| --dur | tokens.css | … |
| --ease | tokens.css | … |
| --glass-edge | tokens.css | … |
| --glass-fill-nested | tokens.css | … |
| --line | tokens.css | … |
| --line-input | tokens.css | … |
| --r-2xs | tokens.css | … |
| --r-lg | tokens.css | … |
| --rgb-brand | tokens.css | … |
| --shadow-hairline-card | tokens.css | … |
| --shadow-input | tokens.css | … |
| --shadow-specular | tokens.css | … |
| --surface-solid | tokens.css | … |
Accessibility
- Each handle is a focusable role="separator" with aria-valuenow (the size of the panel before it), aria-valuemin, aria-valuemax and aria-controls; aria-orientation is perpendicular to the group.
- Arrow keys resize in 5% steps (10% with Shift); Home and End jump to the limits.
- Give each handle an aria-label that says what it resizes when there is more than one.
Do and don't
Do
- Set minSize so no panel can be dragged shut by accident.
- Save the split from onLayout so it survives a reload.
Don't
- Don't use it for layouts people never need to change; fixed columns are simpler.
For AI agents
This page as markdown, rendered from the same registry entry. Point an agent at it (or add .md to this page's URL), or at /llms.txt for the index.
Open resizable.md