Extends the Dialog component to display content that complements the main content of the screen.
Installation
npx shadcn@latest add @caprice/sheetnpm install @caprice-ui/reactInstall the following dependencies:
npm install @base-ui/react lucide-reactAdd a cn helper
import { defineConfig, type VariantProps } from 'cva';import { twMerge } from 'tailwind-merge';export const { compose, cva, cx: cn,} = defineConfig({ hooks: { onComplete: (className: string) => twMerge(className), },});export type { VariantProps };Copy and paste the following code into your project.
'use client';import { mergePropsN, useRender } from '@base-ui/react';import { Dialog as SheetPrimitive } from '@base-ui/react/dialog';import { XIcon } from 'lucide-react';import type * as React from 'react';import { cn, cva, type VariantProps } from '@/lib/utils';/** * Groups all parts of the sheet. * Doesn’t render its own HTML element. * * Documentation: [Caprice UI Sheet](https://caprice-ui.com/docs/components/sheet) * * API Reference: [Base UI Dialog](https://base-ui.com/react/components/dialog#root) */export namespace Root { export type Props = React.ComponentProps<typeof SheetPrimitive.Root>; export type Actions = SheetPrimitive.Root.Actions; export type ChangeEventDetails = SheetPrimitive.Root.ChangeEventDetails; export type ChangeEventReason = SheetPrimitive.Root.ChangeEventReason;}export function Root({ ...props }: Root.Props) { return <SheetPrimitive.Root data-slot="sheet" {...props} />;}/** * A button that opens the sheet. * Renders a `<button>` element. * * Documentation: [Caprice UI Sheet](https://caprice-ui.com/docs/components/sheet) * * API Reference: [Base UI Dialog](https://base-ui.com/react/components/dialog#trigger) */export namespace Trigger { export type State = SheetPrimitive.Trigger.State; export type Props = React.ComponentProps<typeof SheetPrimitive.Trigger>;}export function Trigger({ ...props }: Trigger.Props) { return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;}/** * A button that closes the sheet. * Renders a `<button>` element. * * Documentation: [Caprice UI Sheet](https://caprice-ui.com/docs/components/sheet) * * API Reference: [Base UI Dialog](https://base-ui.com/react/components/dialog#close) */export namespace Close { export type State = SheetPrimitive.Close.State; export type Props = React.ComponentProps<typeof SheetPrimitive.Close>;}export function Close({ ...props }: Close.Props) { return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;}/** * A portal element that moves the popup to a different part of the DOM. * By default, the portal element is appended to `<body>`. * Renders a `<div>` element. * * Documentation: [Caprice UI Sheet](https://caprice-ui.com/docs/components/sheet) * * API Reference: [Base UI Dialog](https://base-ui.com/react/components/dialog#portal) */export namespace Portal { export interface State extends SheetPrimitive.Portal.State {} export type Props = React.ComponentProps<typeof SheetPrimitive.Portal>;}export function Portal({ ...props }: Portal.Props) { return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;}/** * An overlay displayed beneath the popup. * Renders a `<div>` element. * * Documentation: [Caprice UI Sheet](https://caprice-ui.com/docs/components/sheet) * * API Reference: [Base UI Dialog](https://base-ui.com/react/components/dialog#backdrop) */export namespace Backdrop { export type State = SheetPrimitive.Backdrop.State; export type Props = React.ComponentProps<typeof SheetPrimitive.Backdrop>;}export function Backdrop({ className, ...props }: Backdrop.Props) { return ( <SheetPrimitive.Backdrop className={cn( 'fixed inset-0 z-50 bg-black/40 backdrop-blur-[10px] transition-all duration-200 ease-in-out [[data-starting-style],[data-ending-style]]:opacity-0', className )} data-slot="dialog-backdrop" {...props} /> );}const popupVariants = cva({ base: 'fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-closed:animate-out data-open:animate-in data-closed:duration-300 data-open:duration-500', variants: { side: { top: 'data-closed:slide-out-to-top data-open:slide-in-from-top inset-x-0 top-0 h-auto border-b', right: 'data-closed:slide-out-to-right data-open:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm', bottom: 'data-closed:slide-out-to-bottom data-open:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t', left: 'data-closed:slide-out-to-left data-open:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm', }, }, defaultVariants: { side: 'right', },});/** * A container for the sheet contents. * Renders a `<div>` element. * * Documentation: [Caprice UI sheet](https://caprice-ui.com/docs/components/sheet) * * API Reference: [Base UI Dialog](https://base-ui.com/react/components/dialog#popup) */export namespace Popup { export type State = SheetPrimitive.Popup.State; export type Variants = VariantProps<typeof popupVariants>; export type Props = React.ComponentProps<typeof SheetPrimitive.Popup> & Popup.Variants;}export function Popup({ className, children, side = 'right', ...props }: Popup.Props) { return ( <Portal> <Backdrop /> <SheetPrimitive.Popup className={popupVariants({ side, className })} data-slot="sheet-popup" {...props} > {children} <Close className="pointer-coarse:touch-hitbox absolute end-4 top-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-open:bg-secondary [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"> <XIcon className="size-4" /> <span className="sr-only">Close</span> </Close> </SheetPrimitive.Popup> </Portal> );}/** * @deprecated Use {@link Popup} instead * @see {@link https://caprice-ui.com/docs/components/sheet} */export const Content = Popup;/** * A positioning container for the sheet popup that can be made scrollable. * Renders a `<div>` element. * * Documentation: [Caprice UI Sheet](https://caprice-ui.com/docs/components/sheet) * * API Reference: [Base UI Dialog](https://base-ui.com/react/components/dialog#viewport) */export namespace Viewport { export type State = SheetPrimitive.Viewport.State; export type Props = React.ComponentProps<typeof SheetPrimitive.Viewport>;}export function Viewport({ className, ...props }: Viewport.Props) { return ( <SheetPrimitive.Viewport className={cn('', className)} data-slot="sheet-viewport" {...props} /> );}/** * A container for the sheet header. * Renders a `<div>` element. * * Documentation: [Caprice UI Sheet](https://caprice-ui.com/docs/components/sheet) */export namespace Header { export type Props = useRender.ComponentProps<'div'>;}export function Header({ className, render, ...props }: Header.Props) { const defaultProps: useRender.ElementProps<'div'> & { 'data-slot'?: string } = { 'data-slot': 'sheet-header', className: cn('flex flex-col gap-1.5 p-4', className), }; return useRender({ defaultTagName: 'div', render, props: mergePropsN<'div'>([defaultProps, props]), });}/** * A container for the dialog footer. * Renders a `<div>` element. * * Documentation: [Caprice UI Dialog](https://caprice-ui.com/docs/components/dialog) */export namespace Footer { export type Props = useRender.ComponentProps<'div'>;}export function Footer({ className, render, ...props }: Footer.Props) { const defaultProps: useRender.ElementProps<'div'> & { 'data-slot'?: string } = { 'data-slot': 'dialog-footer', className: cn('mt-auto flex flex-col gap-2 p-4', className), }; return useRender({ defaultTagName: 'div', render, props: mergePropsN<'div'>([defaultProps, props]), });}/** * A heading that labels the sheet. * Renders an `<h2>` element. * * Documentation: [Caprice UI Sheet](https://caprice-ui.com/docs/components/sheet) * * API Reference: [Base UI Dialog](https://base-ui.com/react/components/dialog#title) */export namespace Title { export type State = SheetPrimitive.Title.State; export type Props = React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>;}export function Title({ className, ...props }: Title.Props) { return ( <SheetPrimitive.Title className={cn('font-semibold text-lg leading-none', className)} data-slot="sheet-title" {...props} /> );}/** * A paragraph with additional information about the sheet. * Renders a `<p>` element. * * Documentation: [Caprice UI Sheet](https://caprice-ui.com/docs/components/sheet) * * API Reference: [Base UI Dialog](https://base-ui.com/react/components/dialog#description) */export namespace Description { export type State = SheetPrimitive.Description.State; export type Props = React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>;}export function Description({ className, ...props}: React.ComponentProps<typeof SheetPrimitive.Description>) { return ( <SheetPrimitive.Description className={cn('text-muted-foreground text-sm', className)} data-slot="sheet-description" {...props} /> );}Update the import paths to match your project setup.
Usage
import * as Sheet from "@/components/caprice-ui/sheet"import * as Sheet from "@caprice-ui/react/sheet"<Sheet.Root>
<Sheet.Trigger render={<Button>Open</Button>} />
<Sheet.Popup>
<Sheet.Header>
<Sheet.Title>Edit Profile</Sheet.Title>
<Sheet.Description>
Make changes to your profile here. Click save when you're done.
</Sheet.Description>
</Sheet.Header>
<Form className="grid flex-1 auto-rows-min gap-6 px-4">
<Field.Root className="grid gap-3">
<Field.Label htmlFor="sheet-demo-name">Name</Field.Label>
<Input defaultValue="Sacha Osmani" id="sheet-demo-name" />
</Field.Root>
<Field.Root className="grid gap-3">
<Field.Label htmlFor="sheet-demo-username">Username</Field.Label>
<Input defaultValue="@sachaosmani" id="sheet-demo-username" />
</Field.Root>
<Button type="submit">Save</Button>
</Form>
<Sheet.Footer>
<Sheet.Close render={<Button>Close</Button>} />
</Sheet.Footer>
</Sheet.Popup>
</Sheet.Root>Examples
Side
Use the side property to <Sheet.Popup /> to indicate the edge of the screen where the component will appear. The values can be top, right, bottom or left.
Size
You can adjust the size of the sheet using CSS classes:
<Sheet.Root>
<Sheet.Trigger>Open</Sheet.Trigger>
<SheetContent className="w-[400px] sm:w-[540px]">
<Sheet.Header>
<Sheet.Title>Edit Profile</Sheet.Title>
<Sheet.Description>
Make changes to your profile here. Click save when you're done.
</Sheet.Description>
</Sheet.Header>
</SheetContent>
</Sheet.Root>