All components
Splitter
Resizable panel dividers with a separator line and a raised drag handle.
12345678910111213import { Splitter } from "#components/ui/ark-ui/splitter.tsx";
export function Example() { return ( // panel sizing, pointer drag, and arrow-key resize come from the // machine; ResizeTrigger renders its own separator line and drag handle <Splitter.Root panels={[{ id: "a" }, { id: "b" }]} defaultSize={[50, 50]}> <Splitter.Panel id="a">{/* panel a */}</Splitter.Panel> <Splitter.ResizeTrigger id="a:b" /> <Splitter.Panel id="b">{/* panel b */}</Splitter.Panel> </Splitter.Root> );}Install
1npx stylexblocks add splitterSource
components/ui/ark-ui/splitter.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183import * as stylex from "@stylexjs/stylex";import { Splitter as ArkSplitter, useSplitterContext,} from "@ark-ui/react/splitter";import type { WithStyleOverrides } from "#lib/recipe-props.ts";import { colorPalette } from '@stylexblocks/ui/color-palette.stylex.ts'import { radii } from '@stylexblocks/ui/tokens/radii.stylex.ts'import { semanticColors } from '@stylexblocks/ui/semantic-tokens/colors.stylex.ts'import { semanticShadows } from '@stylexblocks/ui/semantic-tokens/shadows.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'
const splitterResizeTrigger = stylex.create({ base: { '--splitter-border-color': { default: semanticColors.border, ':focus': semanticColors.borderEmphasized, }, '--splitter-thumb-color': { default: semanticColors.bg, ':focus': colorPalette.subtle, }, '--splitter-thumb-size': sizes._2, '--splitter-thumb-inset': 'calc(var(--splitter-thumb-size) * -0.5)', '--splitter-border-size': '1px', '--splitter-handle-size': sizes._6, outline: 'none', display: 'grid', placeItems: 'center', position: 'relative', },})
const splitterResizeTriggerSeparator = stylex.create({ base: { position: 'absolute', backgroundColor: 'var(--splitter-border-color)', },})
const splitterResizeTriggerIndicator = stylex.create({ base: { position: 'relative', borderRadius: radii.full, backgroundColor: 'var(--splitter-thumb-color)', boxShadow: semanticShadows.xs, borderWidth: '1px', zIndex: 1, },})
// The generated recipe styles the trigger's colors and vars; the trigger's// browser-button reset and the orientation-dependent geometry (a horizontal// splitter renders a vertical divider, and vice versa) live with the// component. State (drag, keyboard resize) is the machine's.const splitterStatic = stylex.create({ root: { display: "flex", width: "100%", height: "100%" }, panel: { minWidth: 0, minHeight: 0 }, trigger: { appearance: "none", borderWidth: 0, borderStyle: "none", backgroundColor: "transparent", padding: 0, margin: 0, flexShrink: 0, }, triggerVertical: { width: "9px" }, triggerHorizontal: { height: "9px" }, separatorVertical: { top: 0, bottom: 0, left: "calc(50% - 0.5px)", width: "var(--splitter-border-size)", }, separatorHorizontal: { left: 0, right: 0, top: "calc(50% - 0.5px)", height: "var(--splitter-border-size)", }, indicator: { borderStyle: "solid", borderColor: semanticColors.border, }, indicatorVertical: { width: "var(--splitter-thumb-size)", height: "var(--splitter-handle-size)", }, indicatorHorizontal: { height: "var(--splitter-thumb-size)", width: "var(--splitter-handle-size)", },});
function mergeClassName(base: string | undefined, extra: unknown) { return typeof extra === "string" && extra.length > 0 ? [base, extra].filter(Boolean).join(" ") : base;}
/** * Splitter — Ark UI's splitter machine wearing the generated recipe. * `<Splitter.Root panels>` drives panel sizing; `ResizeTrigger` renders the * separator line and drag handle itself (pointer drag and arrow-key resize * are the machine's). Composition per part via `asChild`. */type RootProps = WithStyleOverrides<React.ComponentProps<typeof ArkSplitter.Root>>;
function Root(allProps: RootProps) { const { palette, style, className, ...rest } = allProps; const p = stylex.props(palette, splitterStatic.root, style); return ( <ArkSplitter.Root {...rest} className={mergeClassName(p.className, className)} style={p.style} /> );}Root.displayName = "SplitterRoot";
type PanelProps = WithStyleOverrides<React.ComponentProps<typeof ArkSplitter.Panel>>;
function Panel(allProps: PanelProps) { const { palette, style, className, ...rest } = allProps; const p = stylex.props(palette, splitterStatic.panel, style); return ( <ArkSplitter.Panel {...rest} className={mergeClassName(p.className, className)} style={p.style} /> );}Panel.displayName = "SplitterPanel";
type ResizeTriggerProps = WithStyleOverrides< React.ComponentProps<typeof ArkSplitter.ResizeTrigger>>;
function ResizeTrigger(allProps: ResizeTriggerProps) { const { palette, style, className, children, ...rest } = allProps; // a horizontal splitter lays panels in a row, so its divider is vertical const vertical = useSplitterContext().orientation === "horizontal"; const p = stylex.props( palette, splitterStatic.trigger, splitterResizeTrigger.base, vertical ? splitterStatic.triggerVertical : splitterStatic.triggerHorizontal, style, ); return ( <ArkSplitter.ResizeTrigger {...rest} className={mergeClassName(p.className, className)} style={p.style} > <div {...stylex.props( splitterResizeTriggerSeparator.base, vertical ? splitterStatic.separatorVertical : splitterStatic.separatorHorizontal, )} /> <div {...stylex.props( splitterResizeTriggerIndicator.base, splitterStatic.indicator, vertical ? splitterStatic.indicatorVertical : splitterStatic.indicatorHorizontal, )} /> {children} </ArkSplitter.ResizeTrigger> );}ResizeTrigger.displayName = "SplitterResizeTrigger";
export const Splitter = { Root, Panel, ResizeTrigger };