# Resizable **Category**: react **URL**: https://ui.darkcode.dev/en/docs/react/components/resizable **Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(layout)/resizable.mdx > Resizable panel groups with composable handle types and variants *** ## About The `Resizable` component is built on top of [react-resizable-panels](https://github.com/bvaughn/react-resizable-panels) by [bvaughn](https://github.com/bvaughn), wired up with DarkCode UI design tokens and the compound-component pattern. ## Import ```tsx import { Resizable } from '@darkcode-ui/react'; ``` ## Anatomy Wrap any number of `Resizable.Panel` children in a `Resizable` group, separated by `Resizable.Handle`s. The group sizes each panel as a percentage of its own width (horizontal) or height (vertical). ```tsx Sidebar Main content ``` Every layer is composable: `Resizable.Handle` renders the drag separator, and `Resizable.Indicator` is the inner affordance (pill or drag dots) you can also compose manually. ### Usage ```tsx import {Resizable} from "@darkcode-ui/react"; export function ResizableBasic() { return (
Sidebar
Main content
); } ``` ## Vertical Set `orientation="vertical"` to stack panels and resize along the Y axis. ```tsx import {Resizable} from "@darkcode-ui/react"; export function ResizableVertical() { return (
Editor
Terminal
); } ``` ## Handle Types Three handle affordances — use the one that matches the importance of the boundary. - `line` — minimal 1px separator. Default. Works everywhere. - `drag` — 1px separator + drag-grip chip in the middle. Use when users may not realize the boundary is resizable. - `pill` — 1px separator + rounded pill grip. Highest affordance. Use sparingly, usually for critical boundaries. - `handle` — standalone pill grip with **no separator line**. Use when you want a drag affordance that floats between panels without a continuous divider. ```tsx import {Resizable} from "@darkcode-ui/react"; const types = ["line", "drag", "pill", "handle"] as const; export function ResizableHandleTypes() { return (
{types.map((type) => (
{type}
Left
Right
))}
); } ``` ## Variants Variants map directly to separator tokens so the handle blends into the surface it sits on. - `primary` → `separator` (lightest — default, works on the primary background) - `secondary` → `separator-secondary` (for `background-secondary` / `surface`) - `tertiary` → `separator-tertiary` (for darker tertiary surfaces) ```tsx import {Resizable} from "@darkcode-ui/react"; const variants = ["primary", "secondary", "tertiary"] as const; export function ResizableVariants() { return (
{variants.map((variant) => (
{variant}
Left
Right
))}
); } ``` ## Nested Groups Panels can contain nested `Resizable` groups. Use different orientations to build editor-style three-pane layouts. Give each nested group a unique `id`. ```tsx import {Resizable} from "@darkcode-ui/react"; export function ResizableNested() { return (
Sidebar
Canvas
Console
); } ``` ## Collapsible Panels Set `collapsible` and `collapsedSize` on a panel to let it collapse when dragged below its minimum size. Use `handleRef` for imperative collapse/expand. ```tsx "use client"; import type {ImperativePanelHandle} from "@darkcode-ui/react"; import {Button, Resizable} from "@darkcode-ui/react"; import {useRef, useState} from "react"; export function ResizableCollapsible() { const panelRef = useRef(null); const [collapsed, setCollapsed] = useState(false); return (
setCollapsed(true)} onExpand={() => setCollapsed(false)} >
Sidebar
Main content
); } ``` ## With Indicator Set `withIndicator` on a `line`-type handle to render the drag-dots affordance inline, or compose `Resizable.Indicator` directly for full control. ```tsx import {Resizable} from "@darkcode-ui/react"; export function ResizableWithIndicator() { return (
Files
Preview
); } ``` ## Persisted Sizes ### Client-only apps Pass `autoSaveId` to persist panel sizes to `localStorage`. Reloads restore the previous layout automatically. This is powered by `react-resizable-panels` and works out of the box for client-only apps (Vite, Storybook, etc.). ```tsx Sidebar Main ``` ### SSR-friendly persistence with cookies On SSR frameworks, `localStorage` isn't available on the server, so the first paint uses the default sizes and the client re-renders after hydration. To avoid this flash, use `createCookieStorage` — a built-in helper that returns a cookie-backed `PanelGroupStorage`. Pass it to the `storage` prop alongside `autoSaveId`: ```tsx import {createCookieStorage, Resizable} from '@darkcode-ui/react'; const storage = createCookieStorage(); export function ResizableLayout() { return ( Sidebar Main ); } ``` Reads prefer the cookie (so the server can forward saved layouts) and fall back to `localStorage` for client-only apps; writes go to both so cross-tab readers stay in sync. #### Next.js App Router On the server, read the cookie and forward the saved layout to each panel via `defaultSize`: ```tsx // app/layout.tsx import {cookies} from 'next/headers'; export default async function Layout({children}: {children: React.ReactNode}) { const store = await cookies(); const layout = store.get('react-resizable-panels:app:panels'); let defaultSizes = [30, 70]; if (layout?.value) { try { const parsed = JSON.parse(layout.value); if (Array.isArray(parsed)) defaultSizes = parsed; } catch { // ignore malformed cookie } } return {children}; } ``` ## Styling ### Passing Tailwind CSS classes ```tsx import {Resizable} from '@darkcode-ui/react'; function CustomResizable() { return ( Sidebar Main ); } ``` ### Customizing the component classes To customize the Resizable component classes, you can use the `@layer components` directive. ```css @layer components { .resizable__handle { --resizable-handle-hit-area: 12px; } .resizable__indicator--pill { @apply bg-accent-soft; } } ``` ### CSS Classes The Resizable component uses these CSS classes: #### Base Classes - `.resizable` - Root panel group - `.resizable--horizontal` / `.resizable--vertical` - Orientation modifiers - `.resizable__panel` - Individual panel #### Handle Classes - `.resizable__handle` - Resize handle (separator) - `.resizable__handle--line` / `--drag` / `--pill` / `--handle` - Handle type modifiers - `.resizable__handle--primary` / `--secondary` / `--tertiary` - Emphasis modifiers #### Indicator Classes - `.resizable__indicator` - Grip affordance - `.resizable__indicator--pill` - Rounded pill grip - `.resizable__indicator--drag` - Drag-dots grip #### Data Attributes - `[data-orientation]` on the root — `horizontal` or `vertical` - `[data-resize-handle-state]` on the handle — `inactive`, `hover`, or `drag` - `[data-disabled="true"]` on a disabled handle ## CSS Variables The root `.resizable` element exposes the following variables for easy customization: | Variable | Default | Description | | ----------------------------------- | ----------------------------------- | ------------------------------------ | | `--resizable-handle-size` | `1px` | Visible separator thickness. | | `--resizable-handle-hit-area` | `8px` | Invisible grab area around the line. | | `--resizable-handle-color` | `var(--color-separator)` | Default handle color. | | `--resizable-handle-color-hover` | `var(--color-separator-secondary)` | Hover color. | | `--resizable-handle-color-active` | `var(--color-accent-soft)` | Active/dragging color. | | `--resizable-indicator-pill-width` | `6px` | Pill indicator short axis. | | `--resizable-indicator-pill-height` | `32px` | Pill indicator long axis. | | `--resizable-indicator-drag-size` | `12px` | Drag dots size. | ## API Reference ### Resizable The root panel group. Wraps `react-resizable-panels`' `PanelGroup`. | Prop | Type | Default | Description | | ------------- | --------------------------------- | -------------- | -------------------------------------------------------- | | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction. | | `autoSaveId` | `string` | — | Unique id used to persist panel sizes to `localStorage`. | | `onLayout` | `(sizes: number[]) => void` | — | Fires on every layout change (including while dragging). | | `storage` | `PanelGroupStorage` | `localStorage` | Custom storage implementation for SSR-safe persistence. | | `id` | `string` | — | Unique identifier (required for nested groups). | | `handleRef` | `Ref` | — | Imperative handle (`setLayout`, `getLayout`). | ### Resizable.Panel | Prop | Type | Default | Description | | --------------- | ---------------------------- | ------- | ------------------------------------------------------- | | `defaultSize` | `number` | — | Initial panel size (percent). | | `minSize` | `number` | — | Minimum size (percent). | | `maxSize` | `number` | — | Maximum size (percent). | | `collapsible` | `boolean` | `false` | Whether the panel can collapse to `collapsedSize`. | | `collapsedSize` | `number` | — | Size applied when collapsed. | | `id` | `string` | — | Unique id (required with `autoSaveId` + `collapsible`). | | `order` | `number` | — | Panel render order (for conditional rendering). | | `handleRef` | `Ref` | — | Imperative handle (`collapse`, `expand`, `resize`). | | `onCollapse` | `() => void` | — | Fires when the panel collapses. | | `onExpand` | `() => void` | — | Fires when the panel expands. | | `onResize` | `(size: number) => void` | — | Fires on resize. | ### Resizable.Handle | Prop | Type | Default | Description | | --------------- | ---------------------------------------- | ----------- | -------------------------------------------------------------------------------- | | `type` | `"line" \| "drag" \| "pill" \| "handle"` | `"line"` | Affordance style. `handle` is a standalone pill grip without the separator line. | | `variant` | `"primary" \| "secondary" \| "tertiary"` | `"primary"` | Emphasis level. Maps to separator tokens. | | `withIndicator` | `boolean` | — | Sugar: render the default indicator inside the handle. | | `disabled` | `boolean` | `false` | Whether the handle is disabled. | | `onDragging` | `(isDragging: boolean) => void` | — | Fires when drag starts/ends. | ### Resizable.Indicator | Prop | Type | Default | Description | | ---------- | ------------------ | -------- | ----------------------------------------------- | | `type` | `"pill" \| "drag"` | `"pill"` | Affordance style when no children are provided. | | `children` | `ReactNode` | — | Custom content. Overrides the default affordance. | ### createCookieStorage `createCookieStorage(options?)` returns a cookie-backed `PanelGroupStorage`. | Option | Type | Default | Description | | ---------- | ------------------------------- | ---------- | ---------------------------- | | `maxAge` | `number` | `31536000` | Cookie lifetime in seconds. | | `path` | `string` | `"/"` | Cookie path. | | `sameSite` | `"Lax" \| "Strict" \| "None"` | `"Lax"` | SameSite policy. |