Splitter
A shadcn-style splitter component built with Ark UI primitives.
Panel A
Panel B
import {
Splitter,
SplitterPanel,
SplitterResizeTrigger,
} from "@/components/ui/splitter";
const SplitterBasicDemo = () => (
<div className="h-48 w-full">
<Splitter defaultSize={[40, 60]} panels={[{ id: "a" }, { id: "b" }]}>
<SplitterPanel className="p-4" id="a">
<p className="text-muted-foreground text-sm">Panel A</p>
</SplitterPanel>
<SplitterResizeTrigger id="a:b" withHandle />
<SplitterPanel className="p-4" id="b">
<p className="text-muted-foreground text-sm">Panel B</p>
</SplitterPanel>
</Splitter>
</div>
);
export default SplitterBasicDemo;
Installation
npx shadcn@latest add @ark-cn/splitterInstall the dependency required by this primitive:
npm install @ark-ui/reactCopy the component source into your app:
"use client";
import { Splitter as SplitterPrimitive } from "@ark-ui/react/splitter";
import { cn } from "@/lib/utils";
export type SplitterProps = SplitterPrimitive.RootProps;
export const Splitter = ({ className, ...props }: SplitterProps) => (
<SplitterPrimitive.Root
className={cn(
"flex min-h-0 min-w-0 overflow-hidden rounded-xl border border-border bg-card text-card-foreground",
"data-[orientation=horizontal]:flex-row data-[orientation=vertical]:flex-col",
className,
)}
data-slot="splitter"
{...props}
/>
);
export type SplitterPanelProps = SplitterPrimitive.PanelProps;
export const SplitterPanel = ({ className, ...props }: SplitterPanelProps) => (
<SplitterPrimitive.Panel
className={cn("min-h-0 min-w-0 overflow-auto", className)}
data-slot="splitter-panel"
{...props}
/>
);
export type SplitterResizeTriggerIndicatorProps =
SplitterPrimitive.ResizeTriggerIndicatorProps;
export const SplitterResizeTriggerIndicator = ({
className,
...props
}: SplitterResizeTriggerIndicatorProps) => (
<SplitterPrimitive.ResizeTriggerIndicator
className={cn(
"rounded-full bg-muted-foreground/45",
"data-[orientation=horizontal]:h-8 data-[orientation=horizontal]:w-1",
"data-[orientation=vertical]:h-1 data-[orientation=vertical]:w-8",
className,
)}
data-slot="splitter-handle-indicator"
{...props}
/>
);
export type SplitterResizeTriggerProps =
SplitterPrimitive.ResizeTriggerProps & {
withHandle?: boolean;
indicatorClassName?: string;
};
export const SplitterResizeTrigger = ({
className,
withHandle,
indicatorClassName,
children,
...props
}: SplitterResizeTriggerProps) => (
<SplitterPrimitive.ResizeTrigger
className={cn(
"group/resize relative shrink-0 bg-border/60 outline-none transition-colors",
"hover:bg-border focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
"data-[orientation=horizontal]:w-px data-[orientation=vertical]:h-px",
"data-[orientation=horizontal]:px-px data-[orientation=vertical]:py-px",
"data-disabled:pointer-events-none data-disabled:opacity-64",
className,
)}
data-slot="splitter-handle"
{...props}
>
{withHandle ? (
<span className="pointer-events-none absolute inset-0 group-data-[orientation=horizontal]/resize:-translate-x-1/2 group-data-[orientation=vertical]/resize:-translate-y-1/2 grid place-items-center">
<SplitterResizeTriggerIndicator className={indicatorClassName} />
</span>
) : null}
{children}
</SplitterPrimitive.ResizeTrigger>
);
export const SplitterContext = SplitterPrimitive.Context;
export type {
UseSplitterProps,
UseSplitterReturn,
} from "@ark-ui/react/splitter";
export {
SplitterRootProvider,
useSplitter,
useSplitterContext,
} from "@ark-ui/react/splitter";
Update import aliases to match your project setup.
Usage
import * as Splitter from "@/components/ui/splitter"Read exported parts in src/components/ui/splitter.tsx and compose the primitive according to the Ark UI pattern for this component.
Examples
Vertical
Top
Bottom
import {
Splitter,
SplitterPanel,
SplitterResizeTrigger,
} from "@/components/ui/splitter";
const SplitterVerticalDemo = () => (
<div className="h-64 w-full">
<Splitter
defaultSize={[55, 45]}
orientation="vertical"
panels={[{ id: "top" }, { id: "bottom" }]}
>
<SplitterPanel className="p-4" id="top">
<p className="text-muted-foreground text-sm">Top</p>
</SplitterPanel>
<SplitterResizeTrigger id="top:bottom" withHandle />
<SplitterPanel className="p-4" id="bottom">
<p className="text-muted-foreground text-sm">Bottom</p>
</SplitterPanel>
</Splitter>
</div>
);
export default SplitterVerticalDemo;
Collapsible panels
Content
import {
Splitter,
SplitterPanel,
SplitterResizeTrigger,
} from "@/components/ui/splitter";
const SplitterCollapsiblePanelsDemo = () => (
<div className="h-48 w-full">
<Splitter
defaultSize={[25, 75]}
panels={[
{ id: "sidebar", collapsible: true, collapsedSize: 0, minSize: 15 },
{ id: "content", minSize: 30 },
]}
>
<SplitterPanel className="p-4" id="sidebar">
<p className="text-muted-foreground text-sm">Collapsible sidebar</p>
</SplitterPanel>
<SplitterResizeTrigger id="sidebar:content" withHandle />
<SplitterPanel className="p-4" id="content">
<p className="text-muted-foreground text-sm">Content</p>
</SplitterPanel>
</Splitter>
</div>
);
export default SplitterCollapsiblePanelsDemo;
Multiple panels
Main
Aside (collapsible)
import {
Splitter,
SplitterPanel,
SplitterResizeTrigger,
} from "@/components/ui/splitter";
const SplitterMultiplePanelsDemo = () => (
<div className="h-48 w-full">
<Splitter
defaultSize={[20, 50, 30]}
panels={[
{ id: "nav", minSize: 12 },
{ id: "main", minSize: 30 },
{ id: "aside", collapsible: true, collapsedSize: 0, minSize: 15 },
]}
>
<SplitterPanel className="p-4" id="nav">
<p className="text-muted-foreground text-sm">Nav</p>
</SplitterPanel>
<SplitterResizeTrigger id="nav:main" withHandle />
<SplitterPanel className="p-4" id="main">
<p className="text-muted-foreground text-sm">Main</p>
</SplitterPanel>
<SplitterResizeTrigger id="main:aside" withHandle />
<SplitterPanel className="p-4" id="aside">
<p className="text-muted-foreground text-sm">Aside (collapsible)</p>
</SplitterPanel>
</Splitter>
</div>
);
export default SplitterMultiplePanelsDemo;
Nested
Left
Right · Top
Right · Bottom
import {
Splitter,
SplitterPanel,
SplitterResizeTrigger,
} from "@/components/ui/splitter";
const SplitterNestedDemo = () => (
<div className="h-64 w-full">
<Splitter defaultSize={[35, 65]} panels={[{ id: "left" }, { id: "right" }]}>
<SplitterPanel className="p-4" id="left">
<p className="text-muted-foreground text-sm">Left</p>
</SplitterPanel>
<SplitterResizeTrigger id="left:right" withHandle />
<SplitterPanel className="p-0" id="right">
<Splitter
defaultSize={[60, 40]}
orientation="vertical"
panels={[{ id: "rtop" }, { id: "rbottom" }]}
className="rounded-none"
>
<SplitterPanel className="p-4" id="rtop">
<p className="text-muted-foreground text-sm">Right · Top</p>
</SplitterPanel>
<SplitterResizeTrigger id="rtop:rbottom" withHandle />
<SplitterPanel className="p-4" id="rbottom">
<p className="text-muted-foreground text-sm">Right · Bottom</p>
</SplitterPanel>
</Splitter>
</SplitterPanel>
</Splitter>
</div>
);
export default SplitterNestedDemo;
Context
Dragging: false
Sizes: 50 / 50
SplitterContext exposes the API (e.g. dragging + getSizes()) under the Root.
import {
Splitter,
SplitterContext,
SplitterPanel,
SplitterResizeTrigger,
} from "@/components/ui/splitter";
const SplitterContextDemo = () => (
<div className="flex w-full flex-col gap-3">
<div className="h-44 w-full">
<Splitter
defaultSize={[50, 50]}
panels={[{ id: "left" }, { id: "right" }]}
>
<SplitterPanel className="p-4" id="left">
<SplitterContext>
{(api) => (
<p className="text-muted-foreground text-sm">
Dragging:{" "}
<span className="font-medium text-foreground">
{String(api.dragging)}
</span>
</p>
)}
</SplitterContext>
</SplitterPanel>
<SplitterResizeTrigger id="left:right" withHandle />
<SplitterPanel className="p-4" id="right">
<SplitterContext>
{(api) => (
<p className="text-muted-foreground text-sm">
Sizes:{" "}
<span className="font-medium text-foreground">
{api.getSizes().join(" / ")}
</span>
</p>
)}
</SplitterContext>
</SplitterPanel>
</Splitter>
</div>
<p className="text-muted-foreground text-xs">
SplitterContext exposes the API (e.g. dragging + getSizes()) under the
Root.
</p>
</div>
);
export default SplitterContextDemo;
API reference
This component mirrors the upstream Ark UI primitive. All props and DOM behavior are defined by Ark unless you see an ark-cn-only row below.
SplitterResizeTrigger
| Prop | Type | Description |
|---|---|---|
| withHandle? | boolean | Renders a centered indicator handle. |
| indicatorClassName? | string | Class on the indicator when withHandle is true. |
See the ARK UI documentation for the full API.
Accessibility
See the Ark UI documentation for clarification.