Github

Sheet

Extends the Dialog component to display content that complements the main content of the screen.

"use client";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
	Sheet,
	SheetClose,
	SheetContent,
	SheetDescription,
	SheetFooter,
	SheetHeader,
	SheetTitle,
	SheetTrigger,
} from "@/components/ui/sheet";

export function SheetDemo() {
	return (
		<Sheet>
			<SheetTrigger render={<Button variant="tertiary" />}>Open</SheetTrigger>
			<SheetContent>
				<SheetHeader>
					<SheetTitle>Edit profile</SheetTitle>
					<SheetDescription>
						Make changes to your profile here. Click save when you&apos;re done.
					</SheetDescription>
				</SheetHeader>
				<div className="grid flex-1 auto-rows-min gap-6 px-4">
					<div className="grid gap-3">
						<Label htmlFor="sheet-demo-name">Name</Label>
						<Input
							variant="secondary"
							id="sheet-demo-name"
							defaultValue="Maged Ibrahim"
						/>
					</div>
					<div className="grid gap-3">
						<Label htmlFor="sheet-demo-username">Username</Label>
						<Input
							variant="secondary"
							id="sheet-demo-username"
							defaultValue="@0xMaqed"
						/>
					</div>
				</div>
				<SheetFooter>
					<Button type="submit">Save changes</Button>
					<SheetClose render={<Button variant="tertiary" />}>Close</SheetClose>
				</SheetFooter>
			</SheetContent>
		</Sheet>
	);
}

Installation

pnpm dlx shadcn@latest add https://herocn.dev/r/sheet.json

Usage

import {
  Sheet,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet"
<Sheet>
  <SheetTrigger>Open</SheetTrigger>
  <SheetContent>
    <SheetHeader>
      <SheetTitle>Are you absolutely sure?</SheetTitle>
      <SheetDescription>This action cannot be undone.</SheetDescription>
    </SheetHeader>
  </SheetContent>
</Sheet>

Composition

Use the following composition to build a Sheet:

Sheet
├── SheetTrigger
└── SheetContent
    ├── SheetHeader
    │   ├── SheetTitle
    │   └── SheetDescription
    └── SheetFooter

Examples

Side

Use the side prop on SheetContent to set the edge of the screen where the sheet appears. Values are top, right, bottom, or left.

"use client";

import { Button } from "@/components/ui/button";
import {
	Sheet,
	SheetClose,
	SheetContent,
	SheetDescription,
	SheetFooter,
	SheetHeader,
	SheetTitle,
	SheetTrigger,
} from "@/components/ui/sheet";

const SHEET_SIDES = ["top", "right", "bottom", "left"] as const;

export function SheetSide() {
	return (
		<div className="flex flex-wrap gap-2">
			{SHEET_SIDES.map((side) => (
				<Sheet key={side}>
					<SheetTrigger
						render={<Button variant="tertiary" className="capitalize" />}
					>
						{side}
					</SheetTrigger>
					<SheetContent
						side={side}
						className="data-[side=bottom]:max-h-[50vh] data-[side=top]:max-h-[50vh]"
					>
						<SheetHeader>
							<SheetTitle>Edit profile</SheetTitle>
							<SheetDescription>
								Make changes to your profile here. Click save when you&apos;re
								done.
							</SheetDescription>
						</SheetHeader>
						<div className="no-scrollbar overflow-y-auto px-4">
							{Array.from({ length: 10 }).map((_, index) => (
								<p key={index} className="mb-2 leading-relaxed">
									Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
									do eiusmod tempor incididunt ut labore et dolore magna aliqua.
									Ut enim ad minim veniam, quis nostrud exercitation ullamco
									laboris nisi ut aliquip ex ea commodo consequat. Duis aute
									irure dolor in reprehenderit in voluptate velit esse cillum
									dolore eu fugiat nulla pariatur. Excepteur sint occaecat
									cupidatat non proident, sunt in culpa qui officia deserunt
									mollit anim id est laborum.
								</p>
							))}
						</div>
						<SheetFooter>
							<Button type="submit">Save changes</Button>
							<SheetClose render={<Button variant="tertiary" />}>
								Cancel
							</SheetClose>
						</SheetFooter>
					</SheetContent>
				</Sheet>
			))}
		</div>
	);
}

Overlay variant

Use overlayVariant on SheetContent to choose an opaque dim (opaque), a blurred backdrop (blur), or transparent overlay (transparent).

"use client";

import { Button } from "@/components/ui/button";
import {
	Sheet,
	SheetContent,
	SheetDescription,
	SheetHeader,
	SheetTitle,
	SheetTrigger,
} from "@/components/ui/sheet";

export function SheetOverlayVariant() {
	return (
		<div className="flex flex-wrap gap-2">
			<Sheet>
				<SheetTrigger render={<Button variant="tertiary" />}>
					Opaque
				</SheetTrigger>
				<SheetContent overlayVariant="opaque" className="sm:max-w-sm">
					<SheetHeader>
						<SheetTitle>Opaque overlay</SheetTitle>
						<SheetDescription>
							Default dimmed backdrop without extra blur on the page behind the
							sheet.
						</SheetDescription>
					</SheetHeader>
				</SheetContent>
			</Sheet>
			<Sheet>
				<SheetTrigger render={<Button variant="tertiary" />}>Blur</SheetTrigger>
				<SheetContent overlayVariant="blur" className="sm:max-w-sm">
					<SheetHeader>
						<SheetTitle>Blurred overlay</SheetTitle>
						<SheetDescription>
							Backdrop blur so content behind the sheet is visibly softened.
						</SheetDescription>
					</SheetHeader>
				</SheetContent>
			</Sheet>
			<Sheet>
				<SheetTrigger render={<Button variant="tertiary" />}>
					Transparent
				</SheetTrigger>
				<SheetContent overlayVariant="transparent" className="sm:max-w-sm">
					<SheetHeader>
						<SheetTitle>Transparent overlay</SheetTitle>
						<SheetDescription>
							No dimmed backdrop - the page stays fully visible behind the sheet
							surface.
						</SheetDescription>
					</SheetHeader>
				</SheetContent>
			</Sheet>
		</div>
	);
}

No Close Button

Use showCloseButton={false} on SheetContent to hide the close button.

"use client";

import { Button } from "@/components/ui/button";
import {
	Sheet,
	SheetContent,
	SheetDescription,
	SheetHeader,
	SheetTitle,
	SheetTrigger,
} from "@/components/ui/sheet";

export function SheetNoCloseButton() {
	return (
		<Sheet>
			<SheetTrigger render={<Button variant="tertiary" />}>
				Open Sheet
			</SheetTrigger>
			<SheetContent showCloseButton={false}>
				<SheetHeader>
					<SheetTitle>No close button</SheetTitle>
					<SheetDescription>
						This sheet doesn&apos;t have a close button in the top-right corner.
						Click outside to close.
					</SheetDescription>
				</SheetHeader>
			</SheetContent>
		</Sheet>
	);
}

RTL

To enable RTL support in herocn, see the RTL configuration guide.

"use client";

import {
	type Translations,
	useTranslation,
} from "@/components/language-selector";
import { Button } from "@/components/ui/button";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
	Sheet,
	SheetClose,
	SheetContent,
	SheetDescription,
	SheetFooter,
	SheetHeader,
	SheetTitle,
	SheetTrigger,
} from "@/components/ui/sheet";

const translations: Translations = {
	en: {
		dir: "ltr",
		values: {
			open: "Open",
			editProfile: "Edit profile",
			description:
				"Make changes to your profile here. Click save when you're done.",
			name: "Name",
			username: "Username",
			save: "Save changes",
			close: "Close",
		},
	},
	ar: {
		dir: "rtl",
		values: {
			open: "فتح",
			editProfile: "تعديل الملف الشخصي",
			description:
				"قم بإجراء تغييرات على ملفك الشخصي هنا. انقر حفظ عند الانتهاء.",
			name: "الاسم",
			username: "اسم المستخدم",
			save: "حفظ التغييرات",
			close: "إغلاق",
		},
	},
	he: {
		dir: "rtl",
		values: {
			open: "פתח",
			editProfile: "עריכת פרופיל",
			description: "בצע שינויים בפרופיל שלך כאן. לחץ שמור כשתסיים.",
			name: "שם",
			username: "שם משתמש",
			save: "שמור שינויים",
			close: "סגור",
		},
	},
};

export function SheetRtl() {
	const { dir, t, language } = useTranslation(translations, "ar");

	return (
		<Sheet>
			<SheetTrigger
				data-lang={language}
				render={<Button variant="tertiary">{t.open}</Button>}
			/>
			<SheetContent
				dir={dir}
				side={dir === "rtl" ? "left" : "right"}
				data-lang={dir === "rtl" ? language : undefined}
			>
				<SheetHeader>
					<SheetTitle>{t.editProfile}</SheetTitle>
					<SheetDescription>{t.description}</SheetDescription>
				</SheetHeader>
				<FieldGroup className="px-4">
					<Field>
						<FieldLabel htmlFor="sheet-rtl-name">{t.name}</FieldLabel>
						<Input
							variant="secondary"
							id="sheet-rtl-name"
							defaultValue="Maged Ibrahim"
						/>
					</Field>
					<Field>
						<FieldLabel htmlFor="sheet-rtl-username">{t.username}</FieldLabel>
						<Input
							variant="secondary"
							id="sheet-rtl-username"
							defaultValue="0xMaqed"
						/>
					</Field>
				</FieldGroup>
				<SheetFooter>
					<Button type="submit">{t.save}</Button>
					<SheetClose render={<Button variant="outline">{t.close}</Button>} />
				</SheetFooter>
			</SheetContent>
		</Sheet>
	);
}

API Reference

See the Base UI Dialog documentation.