Files
skills/docs/ui/react/react-ui/src/components/resizable/resizable.md
2026-05-29 08:20:56 +01:00

8.4 KiB

Resizable

(no submodules)

- resizable.variants — `resizablePanelGroupVariants`,…
  • resizable.variants — resizablePanelGroupVariants, resizablePanelVariants, resizableHandleVariants, and resizableHandleGripVariants a…

  • resizable.variants — resizablePanelGroupVariants,…

  • resizable.variants — resizablePanelGroupVariants, resizablePanelVariants, resizableHandleVariants, and resizableHandleGripVariants a…

  • resizable.variants — resizablePanelGroupVariants,…

  • resizable.variants — resizablePanelGroupVariants, resizablePanelVariants, resizableHandleVariants, and resizableHandleGripVariants a…

  • resizable.variants — resizablePanelGroupVariants,…

  • resizable.variants — resizablePanelGroupVariants, resizablePanelVariants, resizableHandleVariants, and resizableHandleGripVariants a…

  • resizable.variants — resizablePanelGroupVariants, resizablePanelVariants, resizableHandleVariants, and resizableHandleGripVariants a…

Purpose

<ResizablePanelGroup>, <ResizablePanel>, and <ResizableHandle> are the chassis split-layout primitives built on react-resizable-panels v4 (Group / Panel / Separator). The library renders a flex container with orientation driving flex-direction (horizontal → row, vertical → column) and exposes pointer + keyboard resize gestures (Arrow keys when the handle is focused). The chassis layers cva slots and an optional withHandle indicator on the separator.

Use this primitive for split-layout surfaces (sidebar / main / preview, master-detail, code-editor + preview). Each panel sizes itself via flex-grow / flex-shrink driven by the library; consumers persist sizes via the defaultLayout / onLayoutChanged props that pass through to the library.

Features

  • Built on react-resizable-panels v4 (Group / Panel / Separator).
  • orientation prop on the group: "horizontal" (default; row layout) or "vertical" (column layout).
  • Pointer drag + keyboard resize (ArrowLeft/Right or ArrowUp/Down when the handle is focused) supplied by the library.
  • defaultSize, minSize, maxSize, collapsible per panel.
  • withHandle opt-in grip indicator on <ResizableHandle> so coarse pointers (touch) get a visible hit indicator.
  • defaultLayout + onLayoutChanged for persisting sizes between sessions.
  • Theme-driven cva overrides for ResizablePanelGroup, ResizablePanel, ResizableHandle, ResizableHandleGrip.
  • data-disabled on the handle when the group's disabled prop is true; the handle still mounts but the cursor and opacity collapse so users see the disabled affordance.

Usage: Basic Example

import {
    ResizableHandle,
    ResizablePanel,
    ResizablePanelGroup,
} from "@suaveplan/react-ui";

export function TwoPaneSplit() {
    return (
        <ResizablePanelGroup id="main-split" className="rounded border">
            <ResizablePanel id="sidebar" defaultSize={25} minSize={15}>
                <div className="p-3">Sidebar</div>
            </ResizablePanel>
            <ResizableHandle id="handle-1" withHandle />
            <ResizablePanel id="main" defaultSize={75} minSize={40}>
                <div className="p-3">Main content</div>
            </ResizablePanel>
        </ResizablePanelGroup>
    );
}

Usage: Advanced Example

import {
    ResizableHandle,
    ResizablePanel,
    ResizablePanelGroup,
} from "@suaveplan/react-ui";
import type { Layout } from "react-resizable-panels";

const STORAGE_KEY = "editor.layout";

function loadLayout(): Layout | undefined {
    try {
        const raw = localStorage.getItem(STORAGE_KEY);
        return raw ? (JSON.parse(raw) as Layout) : undefined;
    } catch {
        return undefined;
    }
}

function saveLayout(layout: Layout): void {
    try {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(layout));
    } catch {
        /* ignore */
    }
}

export function EditorSplit() {
    return (
        <ResizablePanelGroup
            id="editor-split"
            defaultLayout={loadLayout()}
            onLayoutChanged={saveLayout}
            className="h-screen"
        >
            <ResizablePanel id="files" defaultSize={20} minSize={15} collapsible>
                <FileTree />
            </ResizablePanel>
            <ResizableHandle id="h1" withHandle />
            <ResizablePanel id="editor" defaultSize={50} minSize={30}>
                <CodeEditor />
            </ResizablePanel>
            <ResizableHandle id="h2" withHandle />
            <ResizablePanel id="preview" defaultSize={30} minSize={20}>
                <LivePreview />
            </ResizablePanel>
        </ResizablePanelGroup>
    );
}

API Reference

<ResizablePanelGroup>

Wraps Group from react-resizable-panels.

Prop Type Default Description
id string | number useId() Unique identifier within the application. Falls back to React's useId() when omitted.
orientation "horizontal" | "vertical" "horizontal" Layout axis.
defaultLayout Layout ({ [panelId]: number }) none Initial layout (percentages 0..100 keyed by panel id).
onLayoutChange (layout: Layout) => void none Fires on every pointer-move during drag — debounce before persisting.
onLayoutChanged (layout: Layout) => void none Fires once after the pointer is released — recommended for persistence.
disabled boolean false Disable resize across the entire group; the handle gets data-disabled and the cursor stays default.
disableCursor boolean false Disable the library's drag-cursor styling.

<ResizablePanel>

Wraps Panel.

Prop Type Default Description
id string | number useId() Unique identifier within the group.
defaultSize number | string auto Initial size as a percentage (0..100) or a CSS value.
minSize number | string none Minimum size during resize.
maxSize number | string none Maximum size during resize.
collapsible boolean false Allow the panel to collapse below minSize and snap to zero.
onResize (size, id, prev) => void none Fires when the panel's size changes.

<ResizableHandle>

Wraps Separator.

Prop Type Default Description
id string | number useId() Unique identifier within the group.
withHandle boolean false Render a small grip square (using lucide's <GripVerticalIcon>) centred on the handle.
disabled boolean false Disable this individual handle.
disableDoubleClick boolean false Disable double-click-to-collapse on this handle.

Implementation Notes

  • The library v4 names are Group / Panel / Separator; the chassis re-exports them as <ResizablePanelGroup> / <ResizablePanel> / <ResizableHandle> for naming parity with the upstream shadcn registry name and so the chassis surface reads naturally.
  • The library applies display, flex-direction, flex-wrap, and overflow styles inline on the group root and CANNOT be overridden via className — the chassis cva sticks to layout-neutral recipes (h-full w-full).
  • The chassis NEVER assigns a numeric z-index (per the shadcn skill rule). Stacking is implicit — the handle sits between two panels in document order.
  • Each panel renders as an OUTER <div data-panel> wrapping a chassis-className-styled INNER <div>. Tests inspect the inner element when asserting cva classes.
  • The handle exposes aria-orientation perpendicular to the resize axis (a horizontal group has a vertical separator), matching the WAI-ARIA separator pattern.
  • The withHandle grip uses lucide's <GripVerticalIcon>; consumers can render a custom indicator by composing their own children inside <ResizableHandle> instead of opting into withHandle.
  • The disabled group prop sets data-disabled on the handle; the cva collapses the cursor and reduces opacity so users see the disabled state without losing the focus ring.
  • Per the WAI-ARIA separator pattern, the handle is keyboard-focusable (tabindex="0"); ArrowLeft/Right or ArrowUp/Down resize the adjacent panels by 5% per keystroke (library default).