Use Resizable for workspaces, inspectors, and editors. Give the group a bounded size and each panel a useful minimum. The wrappers use the current react-resizable-panels API directly: numeric sizes are pixels; use explicit strings such as "35%" for percentages.
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
const files = ["app/page.tsx", "components/hero.tsx", "styles/theme.css"];
export default function ResizableDemo() {
return (
<ResizablePanelGroup
orientation="horizontal"
className="min-h-[260px] w-full max-w-2xl overflow-hidden rounded-base border-2 border-border bg-background text-foreground shadow-shadow"
>
<ResizablePanel defaultSize="32%" minSize="22%">
<div className="flex h-full min-w-0 flex-col bg-secondary-background">
<div className="border-b-2 border-border px-3 py-2 text-xs font-heading uppercase tracking-wide">
Files
</div>
<ul className="grid gap-1 p-2 text-xs sm:text-sm">
{files.map((file, index) => (
<li
key={file}
className={`min-w-0 truncate rounded-base border-2 border-transparent px-2 py-2 ${
index === 1 ? "border-border bg-main text-main-foreground" : ""
}`}
>
{file}
</li>
))}
</ul>
</div>
</ResizablePanel>
<ResizableHandle withHandle aria-label="Resize file explorer" />
<ResizablePanel defaultSize="68%" minSize="35%">
<ResizablePanelGroup orientation="vertical">
<ResizablePanel defaultSize="64%" minSize="35%">
<div className="flex h-full flex-col">
<div className="border-b-2 border-border px-3 py-2 text-xs font-heading uppercase tracking-wide">
components/hero.tsx
</div>
<pre className="min-h-0 flex-1 overflow-auto whitespace-pre-wrap break-words p-3 font-mono text-[11px] leading-5 sm:text-xs">
{`export function Hero() {\n return <section>Build boldly.</section>\n}`}
</pre>
</div>
</ResizablePanel>
<ResizableHandle withHandle aria-label="Resize preview panel" />
<ResizablePanel defaultSize="36%" minSize="24%">
<div className="flex h-full items-center justify-between gap-3 bg-main p-3 text-main-foreground">
<div className="min-w-0">
<p className="text-xs font-heading uppercase tracking-wide">Preview</p>
<p className="truncate text-sm">Build boldly.</p>
</div>
<span className="rounded-base border-2 border-current px-2 py-1 text-xs font-heading">
Ready
</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</ResizablePanel>
</ResizablePanelGroup>
);
}
#Installation
Complete the base setup. For manual installation:
First time? Install the shared base once.
npx shadcn@latest add https://neobrutal-ui.andongmin.com/r/resizable.jsonnpm install react-resizable-panels@^4.12.2Create components/ui/resizable.tsx:
"use client";
import * as React from "react";
import { GripVertical } from "lucide-react";
import * as ResizablePrimitive from "react-resizable-panels";
import { cn } from "@/lib/utils";
function ResizablePanelGroup({
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.Group>) {
return (
<ResizablePrimitive.Group
data-slot="resizable-panel-group"
className={cn("flex h-full w-full min-w-0 font-base", className)}
{...props}
/>
);
}
function ResizablePanel(props: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />;
}
function ResizableHandle({
withHandle = false,
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.Separator> & { withHandle?: boolean }) {
return (
<ResizablePrimitive.Separator
data-slot="resizable-handle"
className={cn(
"relative flex w-0.5 items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring aria-[orientation=horizontal]:h-0.5 aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:-translate-y-1/2 aria-[orientation=horizontal]:after:translate-x-0 [&[aria-orientation=horizontal]>div]:rotate-90",
className,
)}
{...props}
>
{withHandle && (
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-base border bg-border">
<GripVertical aria-hidden="true" className="size-2.5" />
</div>
)}
</ResizablePrimitive.Separator>
);
}
export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
#Usage
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "@/components/ui/resizable";
export default function SplitWorkspace() {
return (
<ResizablePanelGroup
orientation="horizontal"
className="min-h-48 rounded-base border-2 border-border"
>
<ResizablePanel defaultSize="35%" minSize="20%">
<div className="p-4">Files</div>
</ResizablePanel>
<ResizableHandle withHandle aria-label="Resize file panel" />
<ResizablePanel defaultSize="65%" minSize="30%">
<div className="p-4">Editor</div>
</ResizablePanel>
</ResizablePanelGroup>
);
}#API reference
| Prop / part | Purpose | Default |
|---|---|---|
ResizablePanelGroup.orientation | Split direction | horizontal |
ResizablePanel.defaultSize | Initial size; number = pixels, "35%" = percentage | Distributed by group |
ResizablePanel.minSize / maxSize | Resize constraints in the same units | Upstream defaults |
ResizablePanel.collapsible | Allow collapse | false |
ResizablePanel.panelRef | Native panel imperative handle | Unset |
ResizablePanelGroup.groupRef | Native group imperative handle | Unset |
ResizablePanelGroup.onLayoutChanged | Receive the native layout after resizing | Unset |
ResizableHandle.withHandle | Display a grip | false |
ResizableHandle.disabled | Prevent adjustment | false |
For persisted layouts, use useDefaultLayout from react-resizable-panels and pass its returned layout properties to the group. The wrappers do not implement a second storage system, translate callbacks, or reinterpret native size units.
#Accessibility
Name separators meaningfully. Keep controls usable at the smallest panel size and test keyboard resizing as well as dragging. Set an explicit height for vertical layouts. Use responsive CSS when a split workspace is unsuitable for narrow screens; resizing alone is not a mobile layout.