Components

Resizable

Split-panel primitive (react-resizable-panels) for user-resizable layouts. A ResizablePanelGroup lays panels horizontally or vertically; a ResizableHandle between them exposes a 4px hit area with an optional 12×16 grip (Yuno tokens: bg-border + border-border + rounded-xs, GripVertical light size-2.5). Direction, min/max sizes, and collapsible behavior are per-panel props.

Updated Jul 17, 2026 by Leonardo Posada

Anatomy

Three parts. The Group is the flex container that owns the orientation; Panels are the flex children with size constraints; the Handle is the interactive divider between them.

Editor
Preview
  1. 1
    ResizablePanelGroup

    The flex container. orientation='horizontal' → row (default); orientation='vertical' → column. w-full h-full by default — you set the outer bounds via className (rounded-lg border, fixed height, max-width). Persist layout across reloads with autoSaveId.

  2. 2
    ResizablePanel

    A flex child that responds to drag. Set defaultSize (0-100, %), minSize / maxSize (%) to guard against disappearing content, and optionally collapsible + collapsedSize for panels that can snap closed.

  3. 3
    ResizableHandle

    The interactive divider. Ships a 4px hit area (visible line is 1px) between the two panels. Focusable, keyboard-resizable via arrow keys. withHandle renders the small centered grip so users can see the handle.

  4. 4
    Grip (inside Handle when withHandle)

    12px × 16px pill, rounded-xs (2px), bg-border with border-border, containing a 10px Phosphor DotsSixVertical (weight='light'). Rotates 90° when orientation='vertical'.

Variants

Two axis (orientation) × two handle styles = four presets. Orientation is set on the Group; handle style is set per Handle via the withHandle prop.

Horizontal (default)
Left
Right
<ResizablePanelGroup orientation="horizontal" className="rounded-lg border">
  <ResizablePanel defaultSize={40}>Left</ResizablePanel>
  <ResizableHandle withHandle />
  <ResizablePanel defaultSize={60}>Right</ResizablePanel>
</ResizablePanelGroup>
Vertical
Top
Bottom
<ResizablePanelGroup orientation="vertical" className="rounded-lg border">
  <ResizablePanel defaultSize={40}>Top</ResizablePanel>
  <ResizableHandle withHandle />
  <ResizablePanel defaultSize={60}>Bottom</ResizablePanel>
</ResizablePanelGroup>
Bare 1px divider (no grip)
Left
Right
<ResizablePanelGroup orientation="horizontal" className="rounded-lg border">
  <ResizablePanel defaultSize={50}>Left</ResizablePanel>
  <ResizableHandle />
  <ResizablePanel defaultSize={50}>Right</ResizablePanel>
</ResizablePanelGroup>

States

Handle states: default (1px bg-border), hover (4px hit area picks up the pointer), focus (ring-1 ring-ring ring-offset-1 — keyboard resize with arrow keys), dragging (cursor col-resize / row-resize, panels resize live).

Desktop-only, keyboard-first
Resizable is a mouse + keyboard control (arrow keys move the handle 10% per step). It has no touch equivalent. If you need a resizable region on mobile, swap for a Sheet or Drawer with fixed content.

Recipes

Ready-to-copy compositions covering the most common Yuno usages of this atom.

Editor + preview (canonical)

Horizontal 2-panel split with a grip handle. defaultSize 40/60 with minSize 25 on both so neither panel can disappear. The pattern for any 'code left, preview right' or 'form left, live result right' prototype.

Editor
Preview
<ResizablePanelGroup orientation="horizontal" className="rounded-lg border">
  <ResizablePanel defaultSize={40} minSize={25}>
    {/* Editor */}
  </ResizablePanel>
  <ResizableHandle withHandle />
  <ResizablePanel defaultSize={60} minSize={25}>
    {/* Preview */}
  </ResizablePanel>
</ResizablePanelGroup>
Three panels (sidebar + main + inspector)

Common editor layout: skinny sidebar, wide main, skinny inspector. Set minSize 15/40/15 so the middle panel keeps priority. Two handles, both with grip.

Sidebar
Main
Inspector
<ResizablePanelGroup orientation="horizontal" className="rounded-lg border">
  <ResizablePanel defaultSize={20} minSize={15}>
    {/* Sidebar */}
  </ResizablePanel>
  <ResizableHandle withHandle />
  <ResizablePanel defaultSize={60} minSize={40}>
    {/* Main */}
  </ResizablePanel>
  <ResizableHandle withHandle />
  <ResizablePanel defaultSize={20} minSize={15}>
    {/* Inspector */}
  </ResizablePanel>
</ResizablePanelGroup>
Vertical stack (chat + terminal)

Vertical direction: chat on top, terminal below. Grip rotates 90° automatically. Use when the two regions relate to the same object but need independent height.

Chat
Terminal
<ResizablePanelGroup orientation="vertical" className="rounded-lg border">
  <ResizablePanel defaultSize={60} minSize={30}>
    {/* Chat */}
  </ResizablePanel>
  <ResizableHandle withHandle />
  <ResizablePanel defaultSize={40} minSize={20}>
    {/* Terminal */}
  </ResizablePanel>
</ResizablePanelGroup>
Collapsible sidebar

First panel with collapsible + collapsedSize=0 snaps closed when dragged past the threshold. Wire onCollapse / onExpand to toggle icons or persist state. Skip withHandle here — the collapse gesture is the primary interaction.

Sidebar
Main
const [collapsed, setCollapsed] = React.useState(false);

<ResizablePanelGroup orientation="horizontal" autoSaveId="workspace">
  <ResizablePanel
    defaultSize={20}
    minSize={12}
    collapsible
    collapsedSize={0}
    onCollapse={() => setCollapsed(true)}
    onExpand={() => setCollapsed(false)}
  >
    {/* Sidebar — hides when collapsed */}
  </ResizablePanel>
  <ResizableHandle />
  <ResizablePanel defaultSize={80}>
    {/* Main */}
  </ResizablePanel>
</ResizablePanelGroup>

Import

Single import from @/components/ui/resizable — Group, Panel and Handle in one line.

Three exports, one line.
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";

Props

The most-used props across the three exports. See react-resizable-panels docs for the full API.

PropTypeDefaultDescription
PanelGroup.orientation"horizontal" | "vertical""horizontal"Layout axis. Horizontal = row (default); vertical = column. The grip icon rotates 90° when vertical.
PanelGroup.autoSaveIdstringPersist the current layout to localStorage under this key so it survives reloads. Use a per-prototype key like 'workspace' or 'chat-app'.
PanelGroup.onLayoutChange(sizes: number[]) => voidFires whenever the layout changes. Sizes are percentages that sum to 100.
Panel.defaultSizenumberInitial size (0-100, percent). Should sum to 100 across all panels in the same Group.
Panel.minSizenumber10Lower bound in percent. Guard against content disappearing when the user drags aggressively.
Panel.maxSizenumber100Upper bound in percent. Rarely needed unless one panel must never dominate.
Panel.collapsiblebooleanfalseAllow the user to snap the panel closed by dragging past a threshold. Pair with collapsedSize (usually 0).
Panel.collapsedSizenumber0The size (percent) the panel snaps to when collapsed.
Panel.onCollapse / onExpand() => voidFires when the panel enters / leaves the collapsed state. Wire to icon swaps or persisted state.
Handle.withHandlebooleanfalseWhen true, renders the 12×16 rounded-xs grip centered on the divider (Yuno tokens). Turn on for primary splits; leave off for tertiary dividers where the interaction is secondary.
Handle.disabledbooleanfalseLocks the handle — panels stop responding to drag / arrow keys. Use during read-only states.
Handle.classNamestringMerged onto the divider. Do not override the grip radius / bg / border — those are Yuno tokens.

When to use

  • Editor prototypes (code / preview, form / result).
  • Multi-region workspaces where the user should tune column widths (three-panel apps).
  • Any panel the user should be able to resize or collapse.

When not to use

  • Mobile screens — resizing panels does not map to touch.
  • The Dashboard chrome (Sidebar + TopBar) — that shell is fixed, not user-resizable.
  • Layouts where the size ratio is a product decision, not a user preference — use a plain flex layout with static widths.

Usage

Do
  • Set minSize / maxSize on every Panel so no panel can disappear or dominate.
  • Show the grip (withHandle) whenever the split is a primary interaction — it's the discoverability affordance.
  • Use autoSaveId on the Group to persist the layout across reloads.
  • Wrap the whole Group in a rounded-lg border so the outer bounds read as a single surface.
Don't
  • Don't nest more than 2 levels deep — it stops being clear which handle the user is dragging.
  • Don't hide the handle in a primary split — users need to see something is draggable.
  • Don't override the grip radius, bg or icon — that's the Yuno token spec.
  • Don't use it on mobile prototypes — swap for a Sheet.

Related

Cross-links to atoms and patterns you may reach for next.

  • Scroll areaWrap panel content in ScrollArea so it doesn't clip when the panel gets narrow.
  • SheetFor mobile — Resizable does not translate to touch; use a Sheet or Drawer instead.
  • Sidebar (organism)The Dashboard sidebar is a fixed shell, not a Resizable — don't compose them.
  • CardWrap the whole Group in a Card when it needs surface elevation.