shadcn.io is not affiliated with official shadcn/ui
Components
Free
Resizable for React
Drag a handle between panels so they trade space, for a split editor or a list beside its detail. In React with Base UI, when people should set the sizes themselves.
Framework
UI library
Two panels that share an edge, with a handle that sets how much room each one gets.
Use it when people should choose the split, like a file tree beside code. A column that also holds the app's links is a Sidebar. In React, Base UI exposes the handle as a button named Resize, so the arrow keys work once it has focus.
Installation
Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.
Header over content, starting near a quarter and three quarters, with the handle drawn as a line. Set orientation to vertical for a top and bottom split, such as a toolbar over a canvas. The line can still be focused and dragged. Give the group a height, or a vertical split collapses and the handle sits in an empty gap.
Handle
Sidebar and content start near a quarter and three quarters, with withHandle drawing a grip on the trigger. Use the grip when the split is easy to miss, a faint line on a light page. Without it the target is only thinner, which is enough between panels that already have a strong edge. Either way it is one tab stop, and the arrow keys resize once it is focused.
Rtl
The hero's nested split, with Arabic labels and dir set to rtl on the outer group and the inner vertical group. The horizontal handle still divides the columns, and the inner handle divides the stacked panes. The grip should not need a flipped stylesheet if positioning stays logical. Drag it and confirm the panes grow from the correct edge.
FAQ
How do I set Resizable panel sizes with Base UI?
Give every panel a starting size, then let the drag handle change it. The default orientation is horizontal, so the sizes are widths until you set orientation to vertical.
Set defaultSize on each ResizablePanel as a percentage of the group, and the trigger does not need an id. Listen with onLayoutChanged on Resizable after the pointer is released, and for minSize a number is pixels while a percentage is a string.
How do I stack a Resizable vertically?
Set orientation to vertical on Resizable. The panels stack, and the handle runs across the width. The Vertical demo uses that layout, and a nested group can use a different orientation from its parent. Leave orientation unset, or set it to horizontal, for panels that sit side by side.
How do I show a handle on a Resizable?
Set withHandle on ResizableResizeTrigger to show the grip, as the Handle demo does. Without it, the handle is still a thin line you can drag and focus. The control is a button named Resize, so keyboard users can reach it.
Can I use a Resizable from the keyboard in React?
Yes, focus the resize handle, then use the arrow keys to change the panel sizes. Home and End jump toward the start and end of the range. The handle is one tab stop between the panels. See the Accessibility section for the full key list.
Can I nest a Resizable inside another panel?
Yes, put another Resizable inside a ResizablePanel and give that inner group its own orientation and sizes. The default demo nests a vertical group inside a horizontal one. Each group has its own handles. Sizes on the inner group are relative to that panel, not the page.
How do I style a Resizable?
Edit the resizable file under components/ui. The group sets data-slot to resizable, and the handle sets it to resizable-resize-trigger. Change the line thickness and the grip there. The handle already uses logical positioning, so the Rtl demo does not need a separate layout.
How do I install Resizable in React?
Run the command in the Installation section above. It adds Resizable and its parts to your ui folder. Import from that folder when the command finishes, instead of copying the source by hand, and rerun the command later if you want the latest files.
The command uses the shadcn CLI.
API Reference
Resizable
data-slot="resizable"
PropType
defaultLayout?{ [id: string]: number
Default layout for the Group.
ℹ️ This value allows layouts to be remembered between page reloads.
⚠️ Slight layout shift may occur when server-rendering panels with percentage-based default sizes.
Refer to the documentation for suggestions on how to minimize the impact of this.
onLayoutChange?(layout: Layout) → void
Called when the Group's layout is changing.
⚠️ For layout changes caused by pointer events, this method is called each time the pointer is moved.
For most cases, it is recommended to use the onLayoutChanged callback instead.
Parameters
layout – { id: string]: number }
Returns
void
children?ReactNode
Panel and Separator components that comprise this group.
disableCursor?boolean
This library sets custom mouse cursor styles to indicate drag state.
Use this prop to disable that behavior for Panels and Separators in this group.
disabled?boolean
Disable resize functionality.
elementRef?Ref<HTMLDivElement | null>
Ref attached to the root HTMLDivElement.
Type
Ref<HTMLDivElement | null>
groupRef?Ref<GroupImperativeHandle | null>
Exposes the following imperative API:
- getLayout(): Layout
- setLayout(layout: Layout): void
ℹ️ The useGroupRef and useGroupCallbackRef hooks are exported for convenience use in TypeScript projects.
Type
Ref<GroupImperativeHandle | null>
id?string | number
Uniquely identifies this group within an application.
Falls back to useId when not provided.
ℹ️ This value will also be assigned to the id and data-testid attributes.
Called after the Group's layout has been changed.
ℹ️ For layout changes caused by pointer events, this method is not called until the pointer has been released.
This method is recommended when saving layouts to some storage api.
ℹ️ The second argument contains meta information about the layout change.
The isUserInteraction attribute signals whether the resize was caused by direct user input.
It is true for resizes caused by pointer or keyboard input
and false for other triggers (e.g. imperative API calls, initial mount, etc.)
The requestedLayout attribute is the layout before constraints were applied for the current Group size;
prefer it when persisting layouts.
Type
(layout: Layout, meta: LayoutChangedMeta) → void
Parameters
layout – { id: string]: number }
meta – LayoutChangedMeta
Returns
void
orientation?"horizontal" | "vertical"
Specifies the resizable orientation ("horizontal" or "vertical"); defaults to "horizontal".
Type
"horizontal" | "vertical"
resizePreviewMode?"panel" | "separator"
Controls whether pointer dragging updates Panels sizes immediately,
or renders overlay separator previews until the pointer is released.
Defaults to "panel" (immediate resizing); "separator" defers resizing until release.
Customize previews using the SeparatorOverlay component.
Minimum size of the resizable hit target area (either Separator or Panel edge)
This threshold ensures are large enough to avoid mis-clicks.
- Coarse inputs (typically a finger on a touchscreen) have reduced accuracy;
to ensure accessibility and ease of use, hit targets should be larger to prevent mis-clicks.
- Fine inputs (typically a mouse) can be smaller
ℹ️ [Apple interface guidelines](https://developer.apple.com/design/human-interface-guidelines/accessibility) suggest 20pt (27px) on desktops and 28pt (37px) for touch devices
In practice this seems to be much larger than many of their own applications use though.
Type
{ coarse: number; fine: number; }
style?object
CSS properties.
⚠️ The default inline styles cannot be overridden, except for [overflow](https://react-resizable-panels.vercel.app/examples/overflow).
Distance a collapsible panel must be resized past its minSize to collapse,
or past its collapsedSize to expand.
Defaults to half the distance between collapsedSize and minSize.
For example if a panel declares collapsedSize="5%", collapsedThreshold="5%", and minSize="25%",
it will collapse when resized below 20% and expands when resized above 10%.
ℹ️ Interpretation rules:
- Numbers are interpreted as pixels (e.g. minSize={200} is 200 pixels)
- Strings without explicit units are interpreted as percentage (e.g. minSize="50" is 50 percent)
- Use explicit units (e.g. "px", "%", "em", "rem", "vh", or "vw") to change interpretation.
Type
number | string
collapsible?boolean
This panel can be collapsed.
ℹ️ A collapsible panel will collapse when it's size is less than of the specified minSize.
defaultSize?number | string
Default size of Panel within its parent group; default is auto-assigned based on the total number of Panels.
ℹ️ Interpretation rules:
- Numbers are interpreted as pixels (e.g. defaultSize={200} is 200 pixels)
- Strings without explicit units are interpreted as percentage (e.g. defaultSize="50" is 50 percent)
- Use explicit units (e.g. "px", "%", "em", "rem", "vh", or "vw") to change interpretation
⚠️ Percentage based sizes may cause slight layout shift when server-rendering.
For more information see the documentation.
Type
number | string
disabled?boolean
When disabled, a panel cannot be resized either directly or indirectly (by resizing another panel).
How should this Panel behave if the parent Group is resized?
Defaults to preserve-relative-size.
- preserve-relative-size: Retain the current relative size (as a percentage of the Group)
- preserve-pixel-size: Retain its current size (in pixels)
ℹ️ Panel min/max size constraints may impact this behavior.
⚠️ A Group must contain at least one Panel with preserve-relative-size resize behavior.
Type
"preserve-relative-size" | "preserve-pixel-size"
id?string | number
Uniquely identifies this panel within the parent group.
Falls back to useId when not provided.
ℹ️ This prop is used to associate persisted group layouts with the original panel.
ℹ️ This value will also be assigned to the id and data-testid attributes.
Type
string | number
maxSize?number | string
Maximum size of Panel within its parent group; defaults to "100%".
ℹ️ Interpretation rules:
- Numbers are interpreted as pixels (e.g. maxSize={200} is 200 pixels)
- Strings without explicit units are interpreted as percentage (e.g. maxSize="50" is 50 percent)
- Use explicit units (e.g. "px", "%", "em", "rem", "vh", or "vw") to change interpretation.
Type
number | string
minSize?number | string
Minimum size of Panel within its parent group; defaults to 0%.
ℹ️ Interpretation rules:
- Numbers are interpreted as pixels (e.g. minSize={200} is 200 pixels)
- Strings without explicit units are interpreted as percentage (e.g. minSize="50" is 50 percent)
- Use explicit units (e.g. "px", "%", "em", "rem", "vh", or "vw") to change interpretation.
Called when panel sizes change.
@param panelSize Panel size (both as a percentage of the parent Group and in pixels)
@param id Panel id (if one was provided as a prop)
@param prevPanelSize Previous panel size (will be undefined on mount).
panelSize – { asPercentage: number; inPixels: number }
id – string | number
prevPanelSize – { asPercentage: number; inPixels: number }
Returns
void
panelRef?Ref<PanelImperativeHandle | null>
Exposes the following imperative API:
- collapse(): void
- expand(): void
- getSize(): number
- isCollapsed(): boolean
- resize(size: number): void
ℹ️ The usePanelRef and usePanelCallbackRef hooks are exported for convenience use in TypeScript projects.
Type
Ref<PanelImperativeHandle | null>
style?object
CSS properties.
⚠️ The default inline styles cannot be overridden, except for [overflow](https://react-resizable-panels.vercel.app/examples/overflow).
CSS class name.
⚠️ Class is applied to nested HTMLDivElement to avoid styles that interfere with Flex layout.
ResizableResizeTrigger
data-slot="resizable-resize-trigger"
PropType
disabled?boolean
When disabled, the separator cannot be used to resize its neighboring panels.
ℹ️ The panels may still be resized indirectly (while other panels are being resized).
To prevent a panel from being resized at all, it needs to also be disabled.
disableDoubleClick?boolean
When true, double-clicking this Separator will not reset its Panel to its default size.
elementRef?Ref<HTMLDivElement>
Ref attached to the root HTMLDivElement.
Type
Ref<HTMLDivElement>
id?string | number
Uniquely identifies the separator within the parent group.
Falls back to useId when not provided.
ℹ️ This value will also be assigned to the id and data-testid attributes.
Type
string | number
preview?ReactNode
Overrides the Group default preview for this Separator when resizePreviewMode is "separator".
style?object
CSS properties.
ℹ️ Use the data-separator attribute for custom _hover_ and _active_ styles
⚠️ The following properties cannot be overridden: flex-grow, flex-shrink.
Default layout for the Group.
ℹ️ This value allows layouts to be remembered between page reloads.
⚠️ Slight layout shift may occur when server-rendering panels with percentage-based default sizes.
Refer to the documentation for suggestions on how to minimize the impact of this.
onLayoutChange?(layout: Layout) → void
Called when the Group's layout is changing.
⚠️ For layout changes caused by pointer events, this method is called each time the pointer is moved.
For most cases, it is recommended to use the onLayoutChanged callback instead.
Parameters
layout – { id: string]: number }
Returns
void
children?ReactNode
Panel and Separator components that comprise this group.
disableCursor?boolean
This library sets custom mouse cursor styles to indicate drag state.
Use this prop to disable that behavior for Panels and Separators in this group.
disabled?boolean
Disable resize functionality.
elementRef?Ref<HTMLDivElement | null>
Ref attached to the root HTMLDivElement.
Type
Ref<HTMLDivElement | null>
groupRef?Ref<GroupImperativeHandle | null>
Exposes the following imperative API:
- getLayout(): Layout
- setLayout(layout: Layout): void
ℹ️ The useGroupRef and useGroupCallbackRef hooks are exported for convenience use in TypeScript projects.
Type
Ref<GroupImperativeHandle | null>
id?string | number
Uniquely identifies this group within an application.
Falls back to useId when not provided.
ℹ️ This value will also be assigned to the id and data-testid attributes.
Called after the Group's layout has been changed.
ℹ️ For layout changes caused by pointer events, this method is not called until the pointer has been released.
This method is recommended when saving layouts to some storage api.
ℹ️ The second argument contains meta information about the layout change.
The isUserInteraction attribute signals whether the resize was caused by direct user input.
It is true for resizes caused by pointer or keyboard input
and false for other triggers (e.g. imperative API calls, initial mount, etc.)
The requestedLayout attribute is the layout before constraints were applied for the current Group size;
prefer it when persisting layouts.
Type
(layout: Layout, meta: LayoutChangedMeta) → void
Parameters
layout – { id: string]: number }
meta – LayoutChangedMeta
Returns
void
orientation?"horizontal" | "vertical"
Specifies the resizable orientation ("horizontal" or "vertical"); defaults to "horizontal".
Type
"horizontal" | "vertical"
resizePreviewMode?"panel" | "separator"
Controls whether pointer dragging updates Panels sizes immediately,
or renders overlay separator previews until the pointer is released.
Defaults to "panel" (immediate resizing); "separator" defers resizing until release.
Customize previews using the SeparatorOverlay component.
Minimum size of the resizable hit target area (either Separator or Panel edge)
This threshold ensures are large enough to avoid mis-clicks.
- Coarse inputs (typically a finger on a touchscreen) have reduced accuracy;
to ensure accessibility and ease of use, hit targets should be larger to prevent mis-clicks.
- Fine inputs (typically a mouse) can be smaller
ℹ️ [Apple interface guidelines](https://developer.apple.com/design/human-interface-guidelines/accessibility) suggest 20pt (27px) on desktops and 28pt (37px) for touch devices
In practice this seems to be much larger than many of their own applications use though.
Type
{ coarse: number; fine: number; }
style?object
CSS properties.
⚠️ The default inline styles cannot be overridden, except for [overflow](https://react-resizable-panels.vercel.app/examples/overflow).
When disabled, the separator cannot be used to resize its neighboring panels.
ℹ️ The panels may still be resized indirectly (while other panels are being resized).
To prevent a panel from being resized at all, it needs to also be disabled.
disableDoubleClick?boolean
When true, double-clicking this Separator will not reset its Panel to its default size.
elementRef?Ref<HTMLDivElement>
Ref attached to the root HTMLDivElement.
Type
Ref<HTMLDivElement>
id?string | number
Uniquely identifies the separator within the parent group.
Falls back to useId when not provided.
ℹ️ This value will also be assigned to the id and data-testid attributes.
Type
string | number
preview?ReactNode
Overrides the Group default preview for this Separator when resizePreviewMode is "separator".
style?object
CSS properties.
ℹ️ Use the data-separator attribute for custom _hover_ and _active_ styles
⚠️ The following properties cannot be overridden: flex-grow, flex-shrink.
CSS class name.
ℹ️ Use the data-separator attribute for custom _hover_ and _active_ styles
⚠️ The following properties cannot be overridden: flex-grow, flex-shrink.