Sheet
A sheet component that slides in from the side of the screen, used for displaying additional content or actions without taking up the full screen.
Installation
CLI
npx shadcn@latest add "https://eo-n.vercel.app/r/sheet"
Manual
Copy and paste the following code into your project.
"use client";
import * as React from "react";
import { Dialog as SheetPrimitive } from "@base-ui-components/react/dialog";
import { XIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
}
function SheetTrigger({
...props
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
}
function SheetPortal({
...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
}
function SheetBackdrop({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Backdrop>) {
return (
<SheetPrimitive.Backdrop
data-slot="sheet-backdrop"
className={cn(
"fixed inset-0 z-50 bg-black/50 backdrop-blur-[1.5px] transition-opacity duration-150 ease-out data-[ending-style]:opacity-0 data-[starting-style]:opacity-0",
className
)}
{...props}
/>
);
}
interface SheetContentProps
extends React.ComponentProps<typeof SheetPrimitive.Popup> {
hideCloseIcon?: boolean;
side?: "top" | "right" | "bottom" | "left";
}
function SheetContent({
className,
children,
side = "right",
hideCloseIcon = false,
...props
}: SheetContentProps) {
return (
<SheetPortal>
<SheetBackdrop />
<SheetPrimitive.Popup
data-slot="sheet-content"
data-side={side}
className={cn(
"group bg-background fixed z-50 flex flex-col gap-4 shadow-lg transition-all ease-out data-[closed]:duration-500 data-[open]:duration-300",
"data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=right]:data-[ending-style]:translate-x-full data-[side=right]:data-[starting-style]:translate-x-full sm:data-[side=right]:max-w-sm",
"data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=left]:data-[ending-style]:-translate-x-full data-[side=left]:data-[starting-style]:-translate-x-full sm:data-[side=left]:max-w-sm",
"data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-[ending-style]:-translate-y-full data-[side=top]:data-[starting-style]:-translate-y-full",
"data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:border-b data-[side=bottom]:data-[ending-style]:translate-y-full data-[side=bottom]:data-[starting-style]:translate-y-full",
className
)}
{...props}
>
{children}
{!hideCloseIcon && (
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
<XIcon />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
)}
</SheetPrimitive.Popup>
</SheetPortal>
);
}
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-header"
className={cn("flex flex-col gap-1.5 p-4", className)}
{...props}
/>
);
}
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
);
}
function SheetTitle({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
return (
<SheetPrimitive.Title
data-slot="sheet-title"
className={cn("text-foreground font-semibold", className)}
{...props}
/>
);
}
function SheetDescription({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
return (
<SheetPrimitive.Description
data-slot="sheet-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
}
function SheetClose({
...props
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
}
export {
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetBackdrop,
SheetFooter,
SheetTitle,
SheetDescription,
};
Update the import paths to match your project setup.
Usage
Import all parts and piece them together.
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
<Sheet>
<SheetTrigger>Open</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Are you sure you want to proceed?</SheetTitle>
<SheetDescription>
This action may have permanent effects. Please confirm if you want to
continue.
</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>
Examples
Side
Open On Menu
In order to open a sheet using a menu, control the sheet state and open it imperatively using the onClick
handler on the menu item.
Return Focus Properly
Make sure to also use the sheet’s finalFocus
prop to return focus back
to the menu trigger.
import * as React from "react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuPositioner,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "../ui/sheet";
export default function DropdownMenuRadio() {
const menuTriggerRef = React.useRef<HTMLButtonElement>(null);
const [sheetOpen, setSheetOpen] = React.useState(false);
return (
<React.Fragment>
<DropdownMenu>
{/* Set the trigger ref */}
<DropdownMenuTrigger ref={menuTriggerRef}>Open</DropdownMenuTrigger>
<DropdownMenuPositioner>
<DropdownMenuContent>
{/* Open the sheet when the menu item is clicked */}
<DropdownMenuItem onClick={() => setSheetOpen(true)}>
Open sheet
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPositioner>
</DropdownMenu>
{/* Control the sheet state */}
<Sheet open={sheetOpen} onOpenChange={setSheetOpen}>
{/* Return focus to the menu trigger when the sheet is closed */}
<SheetContent finalFocus={menuTriggerRef}>
<SheetHeader>
<SheetTitle>Are you sure you want to proceed?</SheetTitle>
<SheetDescription>
This action may have permanent effects. Please confirm if you want
to continue.
</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>
</React.Fragment>
);
}
API Reference
Content
Contains content to be rendered in the open sheet.
Prop | Type | Default |
---|---|---|
hideCloseIcon | boolean | false |
side | "top" | "right" | "bottom" | "left" | right |