# Resizable

> Panels split by draggable hairline handles, side by side or stacked, with keyboard resizing and minimum sizes.

Category: Layout and surfaces. Docs page: https://rime.mjsons.net/components/resizable

## Installation

```bash
npx shadcn@latest add KezLahd/rime/resizable
```

ResizablePanelGroup holds ResizablePanel and ResizableHandle children, alternating. Sizes are percentages applied as flex-grow proportions, so handles keep their pixel width and nothing overflows. Each panel can set defaultSize and minSize; onLayout reports every size after a change, for saving the split.

Drag a handle with the pointer, or focus it and use the arrow keys (5%, Shift for 10%) and Home or End. withGrip draws a small grip on the rule. framed (default) puts the group on a glass panel.

## Usage

```tsx
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui";

<ResizablePanelGroup height={320}>
  <ResizablePanel defaultSize={30} minSize={20}>Projects</ResizablePanel>
  <ResizableHandle withGrip />
  <ResizablePanel>Details</ResizablePanel>
</ResizablePanelGroup>
```

## Props: ResizablePanelGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `direction` | `"horizontal" \| "vertical"` | `"horizontal"` | horizontal = panels side by side (drag left and right). Default horizontal. |
| `children` (required) | `ReactNode` |  | ResizablePanel and ResizableHandle children, alternating. |
| `onLayout` | `(sizes: number[]) => void` |  | Called with every panel's size, in percent, after each change. |
| `height` | `number \| string` |  | Group height for vertical groups (or any fixed frame), e.g. 320. |
| `framed` | `boolean` | `true` | Glass frame around the group with hairline handles. Default true. |
| `className` | `string` |  |  |

## Props: ResizablePanel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultSize` | `number` |  | Starting size in percent of the group. Unset panels share what is left. |
| `minSize` | `number` |  | Smallest size in percent. Default 10. |
| `children` | `ReactNode` |  |  |
| `className` | `string` |  |  |

## Props: ResizableHandle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `aria-label` | `string` |  | Accessible name. Defaults to "Resize". |
| `withGrip` | `boolean` |  | Shows a small grip on the rule. |
| `className` | `string` |  |  |

## Examples

### Three panels

Drag a handle, or focus it and use the arrow keys.

```tsx
<ResizablePanelGroup height={240}>
  <ResizablePanel defaultSize={25} minSize={15}>Projects</ResizablePanel>
  <ResizableHandle withGrip />
  <ResizablePanel defaultSize={50}>Invoice INV-004213</ResizablePanel>
  <ResizableHandle withGrip />
  <ResizablePanel defaultSize={25} minSize={15}>Details</ResizablePanel>
</ResizablePanelGroup>
```

### Vertical

```tsx
<ResizablePanelGroup direction="vertical" height={280}>…</ResizablePanelGroup>
```

## Theme tokens

Per-component tokens. Undeclared by default: the default below is the fallback the CSS uses. Set any of them at :root or on a wrapper to restyle every instance.

| Token | Default |
| --- | --- |
| `--resizable-min-height` | `200px` |
| `--resizable-radius` | `var(--r-lg)` |

System tokens it reads: `--blur-panel-light`, `--border-w`, `--brand`, `--dur`, `--ease`, `--glass-edge`, `--glass-fill-nested`, `--line`, `--line-input`, `--r-2xs`, `--r-lg`, `--rgb-brand`, `--shadow-hairline-card`, `--shadow-input`, `--shadow-specular`, `--surface-solid`.

## Accessibility

- Each handle is a focusable role="separator" with aria-valuenow (the size of the panel before it), aria-valuemin, aria-valuemax and aria-controls; aria-orientation is perpendicular to the group.
- Arrow keys resize in 5% steps (10% with Shift); Home and End jump to the limits.
- Give each handle an aria-label that says what it resizes when there is more than one.

## Do

- Set minSize so no panel can be dragged shut by accident.
- Save the split from onLayout so it survives a reload.

## Don't

- Don't use it for layouts people never need to change; fixed columns are simpler.
