Add this component

npx zentauri-ui add resizable-panels

Accessibility notes

Keyboard accessible by default with semantic markup, visible focus treatment, and tokenized states. Add descriptive labels for icon-only or decorative usage.

Dependency notes

Check Installation for shared peers. This component keeps styling in Tailwind classes and the --zui-* token contract.

Layout

Resizable Panels for adjustable workspaces

Compose adjustable workspaces with ResizablePanels, ResizablePanel, and ResizableHandle. Configure percentage limits and collapse behavior while keeping pane content and persistence in your application.

Navigation

Resize or collapse with the keyboard.

Content

A flexible workspace with native controls.

Resizable Panels playground

Adjust panel limits, direction, handles, surfaces, and collapse behavior. The example uses controlled percentages.

Drag the handle, or focus it and use arrow keys. Shift accelerates movement; Home/End use the available bounds. Enter collapses or restores the navigation pane when its neighbour can absorb the space.

Sizes: 35.0% / 65.0%

Last completed resize: Resize a handle to see the callback


Navigation

Collapse this pane with Enter while its resize handle is focused.

Project workspace

Your layout, your space

Drag the divider to make room for the content you are working on. Each pane keeps its own scrolling area.

Appearance gallery

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel A

Panel B

Panel recipes

Uncontrolled split with collapse and restore


Navigation

Resize or collapse with the keyboard.

Content

A flexible workspace with native controls.

Nested horizontal and vertical layouts


Files
Editor preview
Console

Sizing and accessibility rules

Compose alternating Panel, Handle, Panel children; fragments and arrays are supported. Panel IDs must be stable and unique in the document. Constraints are percentages: expanded minima must total at most 100 and maxima at least 100. Invalid values normalize to a feasible layout; invalid constraints or composition throw an actionable error.

Dragging changes the adjacent pair and snaps around collapsed ranges. Enter toggles the preceding collapsible panel; Home/End can also collapse a trailing panel. A zero-size panel remains mounted but becomes inert and hidden from assistive technology. Nonzero collapsed rails remain accessible.

Label each handle for the pane it controls. Horizontal RTL layouts reverse pointer and arrow movement. Vertical layouts need an explicit height, and individual panels scroll. Content padding is applied inside the pane so it does not alter percentage geometry; let the component own flex sizing. Each nested group is independent.

The default handle is 24px wide or tall; use lg for larger touch targets. App code decides when to switch orientation for narrow screens and owns saved layout state. Pointer cancellation rolls back the gesture; disabled-state, orientation, constraint changes, or unmount stop it.

Resizable Panels API

Generated from the package prop types and variant definitions.

ResizableHandle

ResizableHandleProps

Variants

PropTypeDefault
appearance
bluecontrastcyandefaultemeraldglassgradient-bluegradient-greengradient-orangegradient-pinkgradient-purplegray+14
default
size
lgmdsm
md

Behavior

PropTypeDefault
disabledDisable this handle independently.boolean | undefinedfalse
withGripRender the default visual grip when children are absent.boolean | undefinedtrue
Inherited HTML props
PropTypeDefault
childrenReactNodenone
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLDivElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

ResizablePanel

ResizablePanelProps

Variants

PropTypeDefault
appearance
bluecontrastcyandefaultemeraldglassgradient-bluegradient-greengradient-orangegradient-pinkgradient-purplegray+14
default
padding
lgmdnonesm
none

Behavior

PropTypeDefault
collapsedSizeCollapsed percentage, between zero and minSize. Zero makes content inert and hidden.number | undefined0
collapsibleAllow the pane to snap below its expanded minimum; Enter on the following handle toggles it.boolean | undefinedfalse
maxSizeMaximum percentage. All maxima must total at least 100.number | undefined100
minSizeMinimum expanded percentage. All expanded minima must total at most 100.number | undefined10
Inherited HTML props
PropTypeDefault
childrenReactNodenone
classNamestring | undefinednone
id*Stable native DOM id, unique in the document; connects the preceding pane to its handle.stringnone
onClickMouseEventHandler<HTMLDivElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

ResizablePanels

ResizablePanelsProps

Variants

PropTypeDefault
orientationLayout axis; vertical groups need an explicit height.
horizontalvertical
horizontal

Behavior

PropTypeDefault
defaultSizesInitial percentages in panel order; defaults to equal shares.readonly number[] | undefinednone
disabledDisable all resize handles.boolean | undefinedfalse
keyboardStepArrow-key step in percentage points; Shift multiplies it by ten.number | undefined2
onResizeEndLast requested percentages at pointer-up or after a keyboard resize. Not called on cancellation.((sizes: number[]) => void) | undefinednone
onSizesChangeCalled with requested percentages when the user resizes.((sizes: number[]) => void) | undefinednone
sizesControlled percentages in panel order. Invalid values are normalized within constraints.readonly number[] | undefinednone
Inherited HTML props
PropTypeDefault
childrenReactNodenone
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLDivElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

Installation and theme overrides

pnpm add @zentauri-ui/zentauri-components
# Or vendor source:
pnpm dlx @zentauri-ui/zentauri-components add resizable-panels

Add the package to Tailwind v4 source scanning. Adjust the relative path for your stylesheet location; vendored source must also be scanned.

@import "tailwindcss";
@source "../node_modules/@zentauri-ui/zentauri-components";

.my-workspace {
  --zui-resizable-panels-handle-size-md: 28px;
  --zui-resizable-panels-handle-size-md-dark: 28px;
  --zui-resizable-panels-focus-ring: #2563eb;
  --zui-resizable-panels-focus-ring-dark: #60a5fa;
}

Apply these overrides by adding className="my-workspace" to your ResizablePanels root, alongside any layout classes.

Persistence belongs in consumer state: update controlled sizes with onSizesChange and save them with onResizeEnd. Read saved values after hydration or provide server-known values to keep the initial markup deterministic.

CSS variable overrides

Resizable Panels CSS variables

Theme panel surfaces, resize handles, focus rings, grip dimensions, and content padding. Panel percentages are interaction state, not theme tokens.

310 variables

Pattern: --zui-<component>-<slot?>-<variant?>-<property>-<state?>-dark?

:root {
  --zui-resizable-panels-radius: 0.75rem;
  --zui-resizable-panels-border-width: 1px;
  --zui-resizable-panels-border: #e2e8f0;
  --zui-resizable-panels-panel-border-width: 0px;
  --zui-resizable-panels-focus-ring: #2563eb;
  --zui-resizable-panels-handle-size-sm: 12px;
  --zui-resizable-panels-handle-size-md: 24px;
  --zui-resizable-panels-handle-size-lg: 32px;
  --zui-resizable-panels-grip-thickness: 3px;
  --zui-resizable-panels-grip-length: 24px;
  --zui-resizable-panels-panel-padding-sm: 0.5rem;
  --zui-resizable-panels-panel-padding-md: 1rem;
  --zui-resizable-panels-panel-padding-lg: 1.5rem;
  --zui-resizable-panels-panel-default-bg: transparent;
  --zui-resizable-panels-panel-default-fg: inherit;
  --zui-resizable-panels-panel-default-border: transparent;
  --zui-resizable-panels-panel-subtle-bg: #f8fafc;
  --zui-resizable-panels-panel-subtle-fg: #334155;
  --zui-resizable-panels-panel-subtle-border: #e2e8f0;
  --zui-resizable-panels-panel-contrast-bg: #0f172a;
  --zui-resizable-panels-panel-contrast-fg: #f8fafc;
  --zui-resizable-panels-panel-contrast-border: #334155;
  --zui-resizable-panels-panel-glass-bg: #ffffffb3;
  --zui-resizable-panels-panel-glass-fg: #0f172a;
  --zui-resizable-panels-panel-glass-border: #cbd5e1;
  --zui-resizable-panels-panel-glass-blur: 12px;
  --zui-resizable-panels-panel-blue-bg: #eff6ff;
  --zui-resizable-panels-panel-blue-fg: #1e40af;
  --zui-resizable-panels-panel-blue-border: #bfdbfe;
  --zui-resizable-panels-panel-cyan-bg: #ecfeff;
  --zui-resizable-panels-panel-cyan-fg: #155e75;
  --zui-resizable-panels-panel-cyan-border: #a5f3fc;
  --zui-resizable-panels-panel-green-bg: #f0fdf4;
  --zui-resizable-panels-panel-green-fg: #166534;
  --zui-resizable-panels-panel-green-border: #bbf7d0;
  --zui-resizable-panels-panel-lime-bg: #f7fee7;
  --zui-resizable-panels-panel-lime-fg: #3f6212;
  --zui-resizable-panels-panel-lime-border: #d9f99d;
  --zui-resizable-panels-panel-emerald-bg: #ecfdf5;
  --zui-resizable-panels-panel-emerald-fg: #065f46;
  --zui-resizable-panels-panel-emerald-border: #a7f3d0;
  --zui-resizable-panels-panel-indigo-bg: #eef2ff;
  --zui-resizable-panels-panel-indigo-fg: #3730a3;
  --zui-resizable-panels-panel-indigo-border: #c7d2fe;
  --zui-resizable-panels-panel-purple-bg: #faf5ff;
  --zui-resizable-panels-panel-purple-fg: #6b21a8;
  --zui-resizable-panels-panel-purple-border: #e9d5ff;
  --zui-resizable-panels-panel-pink-bg: #fdf2f8;
  --zui-resizable-panels-panel-pink-fg: #9d174d;
  --zui-resizable-panels-panel-pink-border: #fbcfe8;
  --zui-resizable-panels-panel-rose-bg: #fff1f2;
  --zui-resizable-panels-panel-rose-fg: #9f1239;
  --zui-resizable-panels-panel-rose-border: #fecdd3;
  --zui-resizable-panels-panel-sky-bg: #f0f9ff;
  --zui-resizable-panels-panel-sky-fg: #075985;
  --zui-resizable-panels-panel-sky-border: #bae6fd;
  --zui-resizable-panels-panel-teal-bg: #f0fdfa;
  --zui-resizable-panels-panel-teal-fg: #115e59;
  --zui-resizable-panels-panel-teal-border: #99f6e4;
  --zui-resizable-panels-panel-yellow-bg: #fefce8;
  --zui-resizable-panels-panel-yellow-fg: #854d0e;
  --zui-resizable-panels-panel-yellow-border: #fef08a;
  --zui-resizable-panels-panel-orange-bg: #fff7ed;
  --zui-resizable-panels-panel-orange-fg: #9a3412;
  --zui-resizable-panels-panel-orange-border: #fed7aa;
  --zui-resizable-panels-panel-red-bg: #fef2f2;
  --zui-resizable-panels-panel-red-fg: #991b1b;
  --zui-resizable-panels-panel-red-border: #fecaca;
  --zui-resizable-panels-panel-slate-bg: #f8fafc;
  --zui-resizable-panels-panel-slate-fg: #334155;
  --zui-resizable-panels-panel-slate-border: #cbd5e1;
  --zui-resizable-panels-panel-gray-bg: #f9fafb;
  --zui-resizable-panels-panel-gray-fg: #374151;
  --zui-resizable-panels-panel-gray-border: #d1d5db;
  --zui-resizable-panels-panel-zinc-bg: #fafafa;
  --zui-resizable-panels-panel-zinc-fg: #3f3f46;
  --zui-resizable-panels-panel-zinc-border: #d4d4d8;
  --zui-resizable-panels-panel-gradient-blue-from: #eff6ff;
  --zui-resizable-panels-panel-gradient-blue-to: #faf5ff;
  --zui-resizable-panels-panel-gradient-blue-fg: #0f172a;
  --zui-resizable-panels-panel-gradient-blue-border: #bfdbfe;
  --zui-resizable-panels-panel-gradient-green-from: #f0fdf4;
  --zui-resizable-panels-panel-gradient-green-to: #f7fee7;
  --zui-resizable-panels-panel-gradient-green-fg: #0f172a;
  --zui-resizable-panels-panel-gradient-green-border: #bbf7d0;
  --zui-resizable-panels-panel-gradient-purple-from: #faf5ff;
  --zui-resizable-panels-panel-gradient-purple-to: #fdf2f8;
  --zui-resizable-panels-panel-gradient-purple-fg: #0f172a;
  --zui-resizable-panels-panel-gradient-purple-border: #e9d5ff;
  --zui-resizable-panels-panel-gradient-orange-from: #fff7ed;
  --zui-resizable-panels-panel-gradient-orange-to: #fefce8;
  --zui-resizable-panels-panel-gradient-orange-fg: #0f172a;
  --zui-resizable-panels-panel-gradient-orange-border: #fed7aa;
  --zui-resizable-panels-panel-gradient-pink-from: #fdf2f8;
  --zui-resizable-panels-panel-gradient-pink-to: #fff1f2;
  --zui-resizable-panels-panel-gradient-pink-fg: #0f172a;
  --zui-resizable-panels-panel-gradient-pink-border: #fbcfe8;
  --zui-resizable-panels-handle-default-bg: #f1f5f9;
  --zui-resizable-panels-handle-default-fg: #64748b;
  --zui-resizable-panels-handle-subtle-bg: #f8fafc;
  --zui-resizable-panels-handle-subtle-fg: #334155;
  --zui-resizable-panels-handle-contrast-bg: #0f172a;
  --zui-resizable-panels-handle-contrast-fg: #f8fafc;
  --zui-resizable-panels-handle-glass-bg: #ffffffb3;
  --zui-resizable-panels-handle-glass-fg: #0f172a;
  --zui-resizable-panels-handle-glass-blur: 12px;
  --zui-resizable-panels-handle-blue-bg: #eff6ff;
  --zui-resizable-panels-handle-blue-fg: #1e40af;
  --zui-resizable-panels-handle-cyan-bg: #ecfeff;
  --zui-resizable-panels-handle-cyan-fg: #155e75;
  --zui-resizable-panels-handle-green-bg: #f0fdf4;
  --zui-resizable-panels-handle-green-fg: #166534;
  --zui-resizable-panels-handle-lime-bg: #f7fee7;
  --zui-resizable-panels-handle-lime-fg: #3f6212;
  --zui-resizable-panels-handle-emerald-bg: #ecfdf5;
  --zui-resizable-panels-handle-emerald-fg: #065f46;
  --zui-resizable-panels-handle-indigo-bg: #eef2ff;
  --zui-resizable-panels-handle-indigo-fg: #3730a3;
  --zui-resizable-panels-handle-purple-bg: #faf5ff;
  --zui-resizable-panels-handle-purple-fg: #6b21a8;
  --zui-resizable-panels-handle-pink-bg: #fdf2f8;
  --zui-resizable-panels-handle-pink-fg: #9d174d;
  --zui-resizable-panels-handle-rose-bg: #fff1f2;
  --zui-resizable-panels-handle-rose-fg: #9f1239;
  --zui-resizable-panels-handle-sky-bg: #f0f9ff;
  --zui-resizable-panels-handle-sky-fg: #075985;
  --zui-resizable-panels-handle-teal-bg: #f0fdfa;
  --zui-resizable-panels-handle-teal-fg: #115e59;
  --zui-resizable-panels-handle-yellow-bg: #fefce8;
  --zui-resizable-panels-handle-yellow-fg: #854d0e;
  --zui-resizable-panels-handle-orange-bg: #fff7ed;
  --zui-resizable-panels-handle-orange-fg: #9a3412;
  --zui-resizable-panels-handle-red-bg: #fef2f2;
  --zui-resizable-panels-handle-red-fg: #991b1b;
  --zui-resizable-panels-handle-slate-bg: #f8fafc;
  --zui-resizable-panels-handle-slate-fg: #334155;
  --zui-resizable-panels-handle-gray-bg: #f9fafb;
  --zui-resizable-panels-handle-gray-fg: #374151;
  --zui-resizable-panels-handle-zinc-bg: #fafafa;
  --zui-resizable-panels-handle-zinc-fg: #3f3f46;
  --zui-resizable-panels-handle-gradient-blue-from: #eff6ff;
  --zui-resizable-panels-handle-gradient-blue-to: #faf5ff;
  --zui-resizable-panels-handle-gradient-blue-fg: #0f172a;
  --zui-resizable-panels-handle-gradient-green-from: #f0fdf4;
  --zui-resizable-panels-handle-gradient-green-to: #f7fee7;
  --zui-resizable-panels-handle-gradient-green-fg: #0f172a;
  --zui-resizable-panels-handle-gradient-purple-from: #faf5ff;
  --zui-resizable-panels-handle-gradient-purple-to: #fdf2f8;
  --zui-resizable-panels-handle-gradient-purple-fg: #0f172a;
  --zui-resizable-panels-handle-gradient-orange-from: #fff7ed;
  --zui-resizable-panels-handle-gradient-orange-to: #fefce8;
  --zui-resizable-panels-handle-gradient-orange-fg: #0f172a;
  --zui-resizable-panels-handle-gradient-pink-from: #fdf2f8;
  --zui-resizable-panels-handle-gradient-pink-to: #fff1f2;
  --zui-resizable-panels-handle-gradient-pink-fg: #0f172a;
}

/* Dark theme variables follow the same names with -dark appended. */
.dark {
  --zui-resizable-panels-radius-dark: 0.75rem;
  --zui-resizable-panels-border-width-dark: 1px;
  /* ...same variables with -dark at the end */
}

What it does

Horizontal and vertical split layouts with controlled or uncontrolled percentages, per-panel bounds, collapse/restore, and independently themed panel and handle surfaces.

Common use cases

  • Navigation and detail layouts.
  • Code editors with preview and console panes.
  • Nested dashboard and analytics workspaces.
  • Vertical tool panels on narrow screens.

Accessibility

Handles expose focusable separator semantics, accessible labels, value bounds, and controls relationships. Keep panel IDs unique in the document and label each handle for its primary pane.

FAQ

How are panel sizes measured?

Sizes are percentage shares of pane space, excluding resize handles. They total 100; dragging adjusts only the adjacent pair. Every panel has a stable DOM id.

What keyboard controls are available?

Use Left/Right for horizontal layouts or Up/Down for vertical layouts. Shift increases the step by ten. Home/End move to the available limits; Enter collapses or restores the preceding collapsible pane.

Does a vertical layout require a height?

Yes. Set a height on the group or place it inside a parent that establishes one. Panes scroll independently when their content exceeds the available space.

Can I save panel sizes?

Use controlled sizes and onSizesChange in your own state. Save onResizeEnd to avoid writing on every pointer movement. The component does not access localStorage during server rendering.

What happens to collapsed content?

Content remains mounted. A zero-sized pane is inert and hidden from assistive technology; a nonzero collapsed rail remains accessible.

What are the composition rules?

Use direct alternating Panel, Handle, Panel children. Arrays and fragments are supported. Each nested group owns independent state. Expanded minima must total at most 100 and maxima at least 100; invalid composition or constraints throw an actionable error.

Does it need an animation dependency?

No. The static entry uses React and native pointer capture. It respects horizontal RTL direction and supports mouse, pen, and touch.