Resizable
Panels that people can resize by dragging a hairline handle, with keyboard support.
npx prodular-ui add resizableList and detail
A ticket inbox with a draggable grip. The handle turns to the ring color while dragging.
- Pinecrest Health9mInvoice shows the wrong VAT rate
- Corvid Labs26mCannot add a fifth seat
- Tidewater Freight1hWebhook retries are delayed
- Halden Analytics3hExport to Snowflake failed
Invoice shows the wrong VAT rate
Our October invoice applies 19% VAT, but our entity is registered in the Netherlands at 21%. Can you reissue INV-2048 with the right rate?
import { ResizableHandle, ResizablePanel, ResizablePanelGroup,} from "@/components/ui/resizable"
const tickets = [ { id: "#4821", subject: "Invoice shows the wrong VAT rate", from: "Pinecrest Health", time: "9m" }, { id: "#4820", subject: "Cannot add a fifth seat", from: "Corvid Labs", time: "26m" }, { id: "#4818", subject: "Webhook retries are delayed", from: "Tidewater Freight", time: "1h" }, { id: "#4815", subject: "Export to Snowflake failed", from: "Halden Analytics", time: "3h" },]
export default function ResizableHorizontal() { return ( <div className="h-72 w-full overflow-hidden rounded-xl border bg-card shadow-xs"> <ResizablePanelGroup orientation="horizontal"> <ResizablePanel defaultSize="40%" minSize="28%"> <ul className="flex h-full flex-col divide-y overflow-hidden"> {tickets.map((t, i) => ( <li key={t.id} data-active={i === 0 || undefined} className="flex flex-col gap-0.5 px-4 py-3 data-active:bg-brand-subtle" > <div className="flex items-center justify-between gap-2 text-[13px] text-muted-foreground"> <span className="truncate">{t.from}</span> <span className="tabular-nums">{t.time}</span> </div> <span className="truncate text-sm font-medium">{t.subject}</span> </li> ))} </ul> </ResizablePanel> <ResizableHandle withHandle /> <ResizablePanel defaultSize="60%" minSize="36%"> <div className="flex h-full flex-col gap-2 p-5"> <span className="text-[13px] text-muted-foreground tabular-nums">#4821 · Pinecrest Health</span> <h3 className="text-[15px] font-semibold">Invoice shows the wrong VAT rate</h3> <p className="text-sm leading-relaxed text-muted-foreground"> Our October invoice applies 19% VAT, but our entity is registered in the Netherlands at 21%. Can you reissue INV-2048 with the right rate? </p> </div> </ResizablePanel> </ResizablePanelGroup> </div> )}Vertical
An editor above a log. Without withHandle the divider is a plain hairline.
Nightly usage export
Runs every day at 03:00 UTC. Last run succeeded and wrote 38 files.
- 03:00:01Starting nightly usage export
- 03:00:04Fetched 1,284,903 events from 412 workspaces
- 03:02:37Wrote 38 parquet files to s3://halden-exports
- 03:04:12Export finished in 4m 12s
import { ResizableHandle, ResizablePanel, ResizablePanelGroup,} from "@/components/ui/resizable"
const logs = [ ["03:00:01", "Starting nightly usage export"], ["03:00:04", "Fetched 1,284,903 events from 412 workspaces"], ["03:02:37", "Wrote 38 parquet files to s3://halden-exports"], ["03:04:12", "Export finished in 4m 12s"],]
export default function ResizableVertical() { return ( <div className="h-80 w-full max-w-lg overflow-hidden rounded-xl border bg-card shadow-xs"> <ResizablePanelGroup orientation="vertical"> <ResizablePanel defaultSize="55%" minSize="30%"> <div className="flex h-full flex-col gap-1.5 p-5"> <h3 className="text-[15px] font-semibold">Nightly usage export</h3> <p className="text-sm text-muted-foreground"> Runs every day at 03:00 UTC. Last run succeeded and wrote 38 files. </p> </div> </ResizablePanel> <ResizableHandle /> <ResizablePanel defaultSize="45%" minSize="20%"> <ol className="flex h-full flex-col gap-1 overflow-hidden px-5 py-4 font-mono text-[13px]"> {logs.map(([time, line]) => ( <li key={time} className="flex gap-3"> <span className="text-muted-foreground tabular-nums">{time}</span> <span className="truncate">{line}</span> </li> ))} </ol> </ResizablePanel> </ResizablePanelGroup> </div> )}Anatomy
<ResizablePanelGroup orientation="horizontal"> <ResizablePanel defaultSize="40%">...</ResizablePanel> <ResizableHandle withHandle /> <ResizablePanel defaultSize="60%">...</ResizablePanel></ResizablePanelGroup>Guidance
Use it when
- Split views where people decide how much space each side gets: inbox and detail, editor and preview, query and results.
- Inside one container. The panels share its border.
Avoid
- Layouts that never need resizing. Use a fixed grid.
- Borders or cards inside each panel. The handle is the divider.
Rules for AI agents
Published in llms-full.txt, the registry and the MCP server, so coding agents follow them when they build with this component.
- Give the parent of ResizablePanelGroup a fixed height. Panels fill it.
- Pass orientation="horizontal" or "vertical" to ResizablePanelGroup.
- Set defaultSize and minSize on every ResizablePanel as percentage strings, for example defaultSize="40%".
- Add withHandle on handles between panels that people are likely to drag. Leave it off for subtle splits.
- Put the group inside one bordered container (rounded-xl border bg-card) and do not add borders inside the panels.
API
ResizablePanelGroup
| Prop | Type | Default | Description |
|---|---|---|---|
| orientation | "horizontal" | "vertical" | Direction the panels are laid out in. |
ResizablePanel
| Prop | Type | Default | Description |
|---|---|---|---|
| defaultSize | number | string | Initial size, for example "40%". | |
| minSize | number | string | Smallest size the panel can be dragged to. |
ResizableHandle
| Prop | Type | Default | Description |
|---|---|---|---|
| withHandle | boolean | false | Shows a visible grip on the divider. |
Installation
The Prodular UI CLI adds the source to components/ui with everything it depends on. In a project that already uses shadcn/ui, the shadcn CLI installs it from the Prodular registry too. See Installation for the first setup.
npx prodular-ui add resizable# or, with the registry in components.jsonnpx shadcn@latest add @prodular/resizable