Sheet

Slide a panel in from any screen edge for navigation, forms, or details in React. Built on Radix Dialog with four side options. Install via the shadcn CLI.

Report a bug

What it is & when to use

Sheet is a slide-over panel that enters from the edge of the screen, built on Radix UI's Dialog primitive. A side variant positions it on the top, right, bottom, or left, and it ships with trigger, content, header, footer, title, description, and close parts. Use it for navigation drawers, filters, quick forms, or detail views that shouldn't take over the whole page. Radix handles focus trapping, escape-to-close, and ARIA.

Browse all Overlay

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.com/r/sheet.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

3 stories available:

Code

"use client"; import * as SheetPrimitive from "@radix-ui/react-dialog"; import { cva, type VariantProps } from "class-variance-authority"; import { X } from "lucide-react"; import { cn } from "../../lib/utils"; const Sheet = SheetPrimitive.Root; const SheetTrigger = SheetPrimitive.Trigger; const SheetClose = SheetPrimitive.Close; const SheetPortal = SheetPrimitive.Portal; const SheetOverlay = ({ className, ref, ...props }: React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> & { ref?: React.Ref<React.ComponentRef<typeof SheetPrimitive.Overlay>>; }) => ( <SheetPrimitive.Overlay className={cn( "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className, )} {...props} ref={ref} /> ); SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out", { defaultVariants: { side: "right", }, variants: { side: { bottom: "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", }, }, }, ); type SheetContentProps = {} & React.ComponentPropsWithoutRef< typeof SheetPrimitive.Content > & VariantProps<typeof sheetVariants>; const SheetContent = ({ children, className, ref, side = "right", ...props }: SheetContentProps & { ref?: React.Ref<React.ComponentRef<typeof SheetPrimitive.Content>>; }) => ( <SheetPortal> <SheetOverlay /> <SheetPrimitive.Content className={cn(sheetVariants({ side }), className)} ref={ref} {...props} > <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"> <X className="size-4" /> <span className="sr-only">Close</span> </SheetPrimitive.Close> {children} </SheetPrimitive.Content> </SheetPortal> ); SheetContent.displayName = SheetPrimitive.Content.displayName; const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn( "flex flex-col space-y-2 text-center sm:text-left", className, )} {...props} /> ); SheetHeader.displayName = "SheetHeader"; const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn( "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className, )} {...props} /> ); SheetFooter.displayName = "SheetFooter"; const SheetTitle = ({ className, ref, ...props }: React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title> & { ref?: React.Ref<React.ComponentRef<typeof SheetPrimitive.Title>>; }) => ( <SheetPrimitive.Title className={cn("text-lg font-semibold text-foreground", className)} ref={ref} {...props} /> ); SheetTitle.displayName = SheetPrimitive.Title.displayName; const SheetDescription = ({ className, ref, ...props }: React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description> & { ref?: React.Ref<React.ComponentRef<typeof SheetPrimitive.Description>>; }) => ( <SheetPrimitive.Description className={cn("text-sm text-muted-foreground", className)} ref={ref} {...props} /> ); SheetDescription.displayName = SheetPrimitive.Description.displayName; export { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, };

Dependencies

  • @vllnt/ui@^0.3.0

Frequently asked questions

What is Sheet?
Sheet is a slide-over panel component for React that animates in from a screen edge. It is built on Radix UI Dialog and includes trigger, content, header, footer, title, description, and close parts.
How do I add Sheet?
Run `pnpm dlx shadcn@latest add https://ui.vllnt.com/r/sheet.json` to install it into your project via the shadcn CLI.
Which sides can a Sheet open from?
The side prop accepts top, right (the default), bottom, or left, so the panel slides in from whichever edge you choose. Built on Radix Dialog, it also traps focus and closes on Escape.