Resizable
Draggable split panes with a signal-armed seam and an optional sharp grip reticle; horizontal or vertical.
"use client";import { ResizableHandle, ResizablePanel, ResizablePanelGroup,} from "@/registry/okibi/ui/resizable";/** A labeled instrument bay — mono channel header over a centered call-sign. */function Bay({ label, sign }: { label: string; sign: string }) { return ( <div className="flex h-full flex-col gap-2 overflow-hidden p-4"> <span className="font-mono text-[0.6rem] uppercase tracking-grid-wide text-muted-foreground"> {label} </span> <div className="flex flex-1 items-center justify-center font-display text-3xl uppercase tracking-wide text-foreground/80"> {sign} </div> </div> );}export default function ResizableDemo() { return ( <ResizablePanelGroup orientation="horizontal" className="h-80 w-full max-w-3xl border border-border-strong" > <ResizablePanel defaultSize={28} minSize={15}> <Bay label="SEC // NAV" sign="CH-01" /> </ResizablePanel> <ResizableHandle withHandle /> <ResizablePanel defaultSize={72} minSize={30}> <ResizablePanelGroup orientation="vertical"> <ResizablePanel defaultSize={62} minSize={20}> <Bay label="SEC // VIEWPORT" sign="CH-02" /> </ResizablePanel> <ResizableHandle withHandle /> <ResizablePanel defaultSize={38} minSize={20}> <Bay label="SEC // READOUT" sign="CH-03" /> </ResizablePanel> </ResizablePanelGroup> </ResizablePanel> </ResizablePanelGroup> );}npx shadcn@latest add https://okibi.cndr.dev/r/resizable.jsonInstallation
Install the theme first, then add the component:
npx shadcn@latest add https://okibi.cndr.dev/r/resizable.jsonInstall the required dependencies:
npm install lucide-react react-resizable-panelsCopy the source from the Code tab above into components/ui/resizable.tsx. It uses the cn helper and the okibi theme tokens — install the theme first if you haven't.
Usage
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable"
<ResizablePanelGroup orientation="horizontal">
<ResizablePanel defaultSize={50}>One</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={50}>Two</ResizablePanel>
</ResizablePanelGroup>Anatomy
<ResizablePanelGroup orientation="horizontal"> {/* sets the split axis */}
<ResizablePanel defaultSize={50} /> {/* sizing via defaultSize/minSize */}
<ResizableHandle withHandle /> {/* draggable seam + grip reticle */}
<ResizablePanel defaultSize={50} />
</ResizablePanelGroup>Examples
Vertical
Set orientation="vertical" on the group to stack panels and resize along the Y axis — the grip reticle rotates to follow the seam. Nest a vertical group inside a horizontal panel (as the main demo does) for grid layouts.
"use client";import { ResizableHandle, ResizablePanel, ResizablePanelGroup,} from "@/registry/okibi/ui/resizable";export default function ResizableVerticalDemo() { return ( <ResizablePanelGroup orientation="vertical" className="h-80 w-full max-w-md border border-border-strong" > <ResizablePanel defaultSize={55} minSize={20}> <div className="flex h-full items-center justify-center overflow-hidden p-4 font-display text-2xl uppercase tracking-wide text-foreground/80"> CH-01 </div> </ResizablePanel> <ResizableHandle withHandle /> <ResizablePanel defaultSize={45} minSize={20}> <div className="flex h-full items-center justify-center overflow-hidden p-4 font-display text-2xl uppercase tracking-wide text-foreground/80"> CH-02 </div> </ResizablePanel> </ResizablePanelGroup> );}Handle
Pass withHandle to render the sharp grip reticle on the seam — an always-visible drag affordance that arms to signal on hover and drag. Omit it for a minimal hairline seam (the library still provides a wide, accessible hit-target either way).
"use client";import { ResizableHandle, ResizablePanel, ResizablePanelGroup,} from "@/registry/okibi/ui/resizable";export default function ResizableHandleDemo() { return ( <ResizablePanelGroup orientation="horizontal" className="h-52 w-full max-w-md border border-border-strong" > <ResizablePanel defaultSize={50} minSize={20}> <div className="flex h-full items-center justify-center overflow-hidden p-4 font-mono text-xs uppercase tracking-grid text-muted-foreground"> SEC-A </div> </ResizablePanel> <ResizableHandle withHandle /> <ResizablePanel defaultSize={50} minSize={20}> <div className="flex h-full items-center justify-center overflow-hidden p-4 font-mono text-xs uppercase tracking-grid text-muted-foreground"> SEC-B </div> </ResizablePanel> </ResizablePanelGroup> );}API Reference
ResizablePanelGroup
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" | Split axis. (The okibi wrapper exposes orientation, not the library's direction.) |
ResizableHandle
| Prop | Type | Default | Description |
|---|---|---|---|
withHandle | boolean | false | Render the sharp grip reticle on the seam. |
Accessibility
- Built on react-resizable-panels v4, which ships full keyboard resize on the handle: arrow keys nudge the split and
Home/Endjump to the extremes. - The seam takes the standard square signal focus ring (
ring-2 ring-ringwith offset) so keyboard focus on the resize handle is always visible. - Set
withHandleto render the grip reticle — a clear visual affordance that the seam is a draggable, focusable control. - The library owns a wider hit-target than the 1px visual seam, so the drag and focus zone stays comfortable without manual padding.
- Orientation is threaded through context (the v4
Groupownsorientation), so the handle lays its seam and rotates the grip on the correct axis for keyboard and pointer alike.
Guidelines
Do
- Stack panes vertically with
orientation='vertical'on the group — that is the prop the okibi wrapper reads. - Add
withHandlewhen the seam is the primary interaction, so the grip reticle signals it is draggable. - Set sensible
minSize/defaultSizeon panels so a drag can't collapse content to an unusable sliver.
Don't
- Do not pass
direction— the library's prop is shadowed here; the wrapper consumesorientationand the rest is ignored. - Do not wrap the seam in your own hit-target padding; the library already widens the drag zone past the 1px rule.
- Do not put a resizable group inside an auto-height container — it needs a bounded
h-full/w-fullparent to compute splits.
Separator
Technical divider with an optional centered mono label, and an optional live signal pulse that travels along the rule.
ScrollArea
Fixed-height overflow surface with a custom HUD scrollbar — a thin graphite gutter and a sharp thumb that arms to signal on hover, rendered identically across browsers (no native OS chrome). Vertical or horizontal.