A component for building resizable panel groups and layouts.
Installation
npx shadcn@latest add @caprice/resizablepnpm dlx shadcn@latest add @caprice/resizableyarn dlx shadcn@latest add @caprice/resizablebunx shadcn@latest add @caprice/resizablenpm install @caprice-ui/reactpnpm add @caprice-ui/reactyarn add @caprice-ui/reactbun add @caprice-ui/reactInstall the following dependencies:
npm install @base-ui/react lucide-reactpnpm add @base-ui/react lucide-reactyarn add @base-ui/react lucide-reactbun add @base-ui/react lucide-reactAdd a cn helper
import { cn as merge } from 'cn';
import type { VariantProps } from 'cva';
import { defineConfig } from 'cva/config';
import { getSchema } from 'cva/tools';
export const { cva, cx: cn } = defineConfig({
cx: merge,
});
export type { VariantProps };
export { getSchema };
Copy and paste the following code into your project.
'use client';
import { GripVerticalIcon } from 'lucide-react';
import type * as React from 'react';
import * as ResizablePrimitive from 'react-resizable-panels';
import { cn } from '@/lib/utils';
/**
* A Group wraps a set of resizable Panel components.
* Group content can be resized _horizontally_ or _vertically_.
*
* Group elements always include the following attributes:
*
* ```html
* <div data-group data-testid="group-id-prop" id="group-id-prop">
* ```
*
* Documentation: [Caprice UI Resizable](https://caprice-ui.com/docs/components/resizable)
*
* API Reference: [React Resizable Panels](https://react-resizable-panels.com/docs/group)
*/
export namespace Group {
export type Props = React.ComponentProps<typeof ResizablePrimitive.Group>;
}
export function Group({ ...props }: Group.Props) {
return (
<ResizablePrimitive.Group
className={cn('size-full', props.className)}
data-slot="resizable-group"
{...props}
/>
);
}
/**
* A Panel wraps resizable content and can be configured with min/max size constraints and collapsible behavior.
*
* Panel size props can be specified using the following CSS units:
* - Pixels (default if value is of type `number`)
* - Percentages (default if value is of type `string`)
* - Font sizes (em, rem)
* - Viewport sizes (vh, vw)
*
* Panel elements always include the following attributes:
*
* ```html
* <div data-panel data-testid="panel-id-prop" id="panel-id-prop">
* ```
*
* Documentation: [Caprice UI Resizable](https://caprice-ui.com/docs/components/resizable)
*
* API Reference: [React Resizable Panels](https://react-resizable-panels.com/docs/panel)
*/
export namespace Panel {
export type Props = React.ComponentProps<typeof ResizablePrimitive.Panel>;
}
export function Panel({ ...props }: Panel.Props) {
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />;
}
/**
* Separators are not _required_ but they are _recommended_ as they improve keyboard accessibility.
*
* Separators should be rendered as the direct child of a Group component.
*
* Separator elements always include the following attributes:
*
* ```html
* <div data-separator data-testid="separator-id-prop" id="separator-id-prop" role="separator">
* ```
*
* Documentation: [Caprice UI Resizable](https://caprice-ui.com/docs/components/resizable)
*
* API Reference: [React Resizable Panels](https://react-resizable-panels.com/docs/separator)
*/
export namespace Separator {
export type Props = React.ComponentProps<typeof ResizablePrimitive.Separator> & {
/**
* Whether to show the handle.
*
* @default false
*/
withHandle?: boolean;
};
}
export function Separator({ withHandle, className, ...props }: Separator.Props) {
return (
<ResizablePrimitive.Separator
className={cn(
'relative flex w-px 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-hidden focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 aria-[orientation=horizontal]:h-px 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-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90',
className
)}
data-slot="resizable-separator"
{...props}
>
{withHandle && (
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-xs border bg-border">
<GripVerticalIcon className="size-2.5" />
</div>
)}
</ResizablePrimitive.Separator>
);
}
Update the import paths to match your project setup.
Usage
import * as Resizable from "@/components/caprice-ui/resizable"import * as Resizable from "@caprice-ui/react/resizable"Examples
Vertical
Handle
Nested Group
Collapsible
Differences with shadcn/ui
If you're familiar with shadcn/ui, most patterns will carry over. This guide points out the differences so you can start using Caprice UI without surprises.
Key changes
| Feature | shadcn/ui | Caprice UI |
|---|---|---|
| Import style | Individual named imports | Namespace import (* as Resizable) |
| Group component | ResizablePanelGroup | Resizable.Group |
| Panel component | ResizablePanel | Resizable.Panel |
| Handle component | ResizableHandle | Resizable.Separator |
| Orientation prop | direction | orientation |
| Orientation detection | data-[panel-group-direction=vertical] | aria-[orientation=horizontal] |
| Data slot (group) | resizable-panel-group | resizable-group |
| Data slot (handle) | resizable-handle | resizable-separator |
| Type exports | Regular types | Namespace types (Group.Props, Panel.Props, Separator.Props) |
The component is renamed from "Handle" to "Separator" to align with the ARIA separator role, which is the semantic role used by react-resizable-panels for keyboard accessibility.
Comparison Example
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable';
<ResizablePanelGroup direction="horizontal">
<ResizablePanel defaultSize={25}>Sidebar</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={75}>Content</ResizablePanel>
</ResizablePanelGroup>import * as Resizable from '@/components/caprice-ui/resizable';
<Resizable.Group orientation="horizontal">
<Resizable.Panel defaultSize={25}>Sidebar</Resizable.Panel>
<Resizable.Separator withHandle />
<Resizable.Panel defaultSize={75}>Content</Resizable.Panel>
</Resizable.Group>Accessibility
- Keyboard navigation: Separators can be focused and resized using arrow keys, following the ARIA separator pattern.
- ARIA orientation: The component uses
aria-orientationto communicate the resize direction to assistive technologies. - Focus indicator: Separators display a visible focus ring when focused via keyboard.
Panel
Separator
Last updated on