Skip to content
shadcn.io

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.

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

Anatomy

Resizable anatomy: root, panel, resize trigger, indicator
Resizable
├── ResizablePanel
└── ResizableResizeTrigger
    └── ResizableResizeTriggerIndicator

Usage

import {
  Resizable, ResizablePanel, ResizableResizeTrigger,
} from "@/components/ui/resizable";
const ResizableDemo = () => (
  <Resizable className="max-w-sm rounded-lg border" orientation="horizontal">
    <ResizablePanel defaultSize="50%">
      <div className="flex h-[200px] items-center justify-center p-6">
        <span className="font-semibold">One</span>
      </div>
    </ResizablePanel>
    <ResizableResizeTrigger withHandle />
    <ResizablePanel defaultSize="50%">
      <Resizable orientation="vertical">
        <ResizablePanel defaultSize="25%">
          <div className="flex h-full items-center justify-center p-6">
            <span className="font-semibold">Two</span>
          </div>
        </ResizablePanel>
        <ResizableResizeTrigger withHandle />
        <ResizablePanel defaultSize="75%">
          <div className="flex h-full items-center justify-center p-6">
            <span className="font-semibold">Three</span>
          </div>
        </ResizablePanel>
      </Resizable>
    </ResizablePanel>
  </Resizable>
);

Accessibility

Enter
Activate the focused item.
Arrow Up
Move up.
Arrow Down
Move down.
Arrow Left
Move left.
Arrow Right
Move right.
Home
Go to the start.
End
Go to the end.
F6
Handle the F6 key.

Demos

Vertical

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.

Preview width

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.

Preview width

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.

Preview width

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.

Type
string | number
onLayoutChanged?(layout: Layout, meta: LayoutChangedMeta) → void

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.

Type
"panel" | "separator"
resizeTargetMinimumSize?{ coarse: number; fine: number; }

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).

Type
{ prop?: string]: string | number }
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

ResizablePanel

data-slot="resizable-panel"

PropType
collapsedSize?number | string

Panel size when collapsed; defaults to 0%.

Type
number | string
collapsedThreshold?number | string

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).

elementRef?Ref<HTMLDivElement | null>

Ref attached to the root HTMLDivElement.

Type
Ref<HTMLDivElement | null>
groupResizeBehavior?"preserve-relative-size" | "preserve-pixel-size"

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.

Type
number | string
onResize?(panelSize: PanelSize, id: string | number, prevPanelSize: PanelSize) → void

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).

Type
(panelSize: PanelSize, id: string | number, prevPanelSize: PanelSize) → void
Parameters

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).

Type
{ prop?: string]: string | number }
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

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.

Type
{ prop?: string]: string | number }
withHandle?boolean

Whether to show the handle.

Default
false
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

ResizablePanelGroup

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.

Type
string | number
onLayoutChanged?(layout: Layout, meta: LayoutChangedMeta) → void

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.

Type
"panel" | "separator"
resizeTargetMinimumSize?{ coarse: number; fine: number; }

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).

Type
{ prop?: string]: string | number }
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

CSS class name.

ResizableHandle

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.

Type
{ prop?: string]: string | number }
withHandle?boolean

Whether to show the handle.

Default
false
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

CSS class name. ℹ️ Use the data-separator attribute for custom _hover_ and _active_ styles ⚠️ The following properties cannot be overridden: flex-grow, flex-shrink.

With other components

  • Free

    Sidebar

    Navigation

    Nav in a panel

  • Free

    Overflow inside a panel

  • Free

    Tabs

    Navigation

    Switch panel content

  • Free

    Card

    Layout

    Frame around the group

  • Free

    Table

    Data

    List beside a preview

  • Free

    Button

    Buttons

    Collapse from a click