Vela UI Docsvenizia.ai

Sheet

A side panel that slides in over the page for details, forms, and secondary flows.

npx shadcn@latest add @velaui/sheet
npx shadcn@latest add @velaui/sheet

Examples

  • Default
  • Badge and tabs
  • Additional action
  • Side left
  • Form
  • Side bottom

View code

Sheet · Default

Installation

pnpm dlx velaui add @velaui/sheet
pnpm dlx velaui add @velaui/sheet

Code

sheet-default.tsx
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";

export default function Example() {
  return (
    <Sheet>
      <SheetTrigger asChild>
        <Button variant="primary">Open panel</Button>
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetHeaderContent>
            <SheetTitleGroup>
              <SheetTitle>Master Heading</SheetTitle>
              <SheetDescription>
                A side panel slides in over the page to show details, forms or
                secondary flows without losing the current context.
              </SheetDescription>
            </SheetTitleGroup>
            <SheetCloseButton />
          </SheetHeaderContent>
        </SheetHeader>
        <SheetBody>
          <p className="text-sm text-text-muted">
            Everything the panel has to say goes here. The body scrolls on its
            own, so the heading and the actions stay put.
          </p>
        </SheetBody>
        <SheetFooter>
          <SheetFooterActions>
            <SheetClose asChild>
              <Button variant="secondary">Cancel</Button>
            </SheetClose>
            <SheetClose asChild>
              <Button variant="primary">Save changes</Button>
            </SheetClose>
          </SheetFooterActions>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";

export default function Example() {
  return (
    <Sheet>
      <SheetTrigger render={<Button variant="primary" />}>
        Open panel
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetHeaderContent>
            <SheetTitleGroup>
              <SheetTitle>Master Heading</SheetTitle>
              <SheetDescription>
                A side panel slides in over the page to show details, forms or
                secondary flows without losing the current context.
              </SheetDescription>
            </SheetTitleGroup>
            <SheetCloseButton />
          </SheetHeaderContent>
        </SheetHeader>
        <SheetBody>
          <p className="text-sm text-text-muted">
            Everything the panel has to say goes here. The body scrolls on its
            own, so the heading and the actions stay put.
          </p>
        </SheetBody>
        <SheetFooter>
          <SheetFooterActions>
            <SheetClose render={<Button variant="secondary" />}>
              Cancel
            </SheetClose>
            <SheetClose render={<Button variant="primary" />}>
              Save changes
            </SheetClose>
          </SheetFooterActions>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}

View code

Sheet · Badge and tabs

Installation

pnpm dlx velaui add @velaui/sheet
pnpm dlx velaui add @velaui/sheet

Code

sheet-badge-and-tabs.tsx
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
} from "@/components/ui/sheet";
import {
  Tabs,
  TabsContent,
  TabsList,
  TabsTrigger,
} from "@/components/ui/tabs";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";

export default function Example() {
  return (
    <Sheet>
      <SheetTrigger asChild>
        <Button variant="secondary">Account settings</Button>
      </SheetTrigger>
      <SheetContent>
        <Tabs defaultValue="details" className="contents">
          <SheetHeader>
            <SheetHeaderContent>
              <SheetTitleGroup>
                <SheetTitle>Master Heading</SheetTitle>
                <SheetDescription>Description</SheetDescription>
              </SheetTitleGroup>
              <Badge dot>Badge</Badge>
              <SheetCloseButton />
            </SheetHeaderContent>
            <TabsList variant="underline" size="sm" fullWidth>
              <TabsTrigger value="details">My details</TabsTrigger>
              <TabsTrigger value="profile">Profile</TabsTrigger>
              <TabsTrigger value="password">Password</TabsTrigger>
              <TabsTrigger value="team">Team</TabsTrigger>
              <TabsTrigger value="notifications">
                Notifications
                <Badge variant="subtle" size="sm">
                  2
                </Badge>
              </TabsTrigger>
            </TabsList>
          </SheetHeader>
          <SheetBody>
            <TabsContent value="details">Your details.</TabsContent>
            <TabsContent value="profile">Your public profile.</TabsContent>
            <TabsContent value="password">Change your password.</TabsContent>
            <TabsContent value="team">Everyone on your team.</TabsContent>
            <TabsContent value="notifications">
              What we email you about.
            </TabsContent>
          </SheetBody>
        </Tabs>
        <SheetFooter>
          <SheetFooterActions>
            <SheetClose asChild>
              <Button variant="secondary">Cancel</Button>
            </SheetClose>
            <SheetClose asChild>
              <Button variant="primary">Save</Button>
            </SheetClose>
          </SheetFooterActions>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
} from "@/components/ui/sheet";
import {
  Tabs,
  TabsContent,
  TabsList,
  TabsTrigger,
} from "@/components/ui/tabs";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";

export default function Example() {
  return (
    <Sheet>
      <SheetTrigger render={<Button variant="secondary" />}>
        Account settings
      </SheetTrigger>
      <SheetContent>
        <Tabs defaultValue="details" className="contents">
          <SheetHeader>
            <SheetHeaderContent>
              <SheetTitleGroup>
                <SheetTitle>Master Heading</SheetTitle>
                <SheetDescription>Description</SheetDescription>
              </SheetTitleGroup>
              <Badge dot>Badge</Badge>
              <SheetCloseButton />
            </SheetHeaderContent>
            <TabsList variant="underline" size="sm" fullWidth>
              <TabsTrigger value="details">My details</TabsTrigger>
              <TabsTrigger value="profile">Profile</TabsTrigger>
              <TabsTrigger value="password">Password</TabsTrigger>
              <TabsTrigger value="team">Team</TabsTrigger>
              <TabsTrigger value="notifications">
                Notifications
                <Badge variant="subtle" size="sm">
                  2
                </Badge>
              </TabsTrigger>
            </TabsList>
          </SheetHeader>
          <SheetBody>
            <TabsContent value="details">Your details.</TabsContent>
            <TabsContent value="profile">Your public profile.</TabsContent>
            <TabsContent value="password">Change your password.</TabsContent>
            <TabsContent value="team">Everyone on your team.</TabsContent>
            <TabsContent value="notifications">
              What we email you about.
            </TabsContent>
          </SheetBody>
        </Tabs>
        <SheetFooter>
          <SheetFooterActions>
            <SheetClose render={<Button variant="secondary" />}>
              Cancel
            </SheetClose>
            <SheetClose render={<Button variant="primary" />}>Save</SheetClose>
          </SheetFooterActions>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}

View code

Sheet · Additional action

Installation

pnpm dlx velaui add @velaui/sheet
pnpm dlx velaui add @velaui/sheet

Code

sheet-additional-action.tsx
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { Trash01 } from "@/components/icons/trash-01";

/**
 * The footer's action group takes the free space, so an additional action
 * placed before it sits hard left while the primary pair stays right.
 */
export default function Example() {
  return (
    <Sheet>
      <SheetTrigger asChild>
        <Button variant="outline">Edit invoice</Button>
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetHeaderContent>
            <SheetTitleGroup>
              <SheetTitle>Invoice #1042</SheetTitle>
              <SheetDescription>
                Issued 12 March 2026 · Due in 14 days
              </SheetDescription>
            </SheetTitleGroup>
            <SheetCloseButton />
          </SheetHeaderContent>
        </SheetHeader>
        <SheetBody>
          <p className="text-sm text-text-muted">
            Line items, taxes and payment history for this invoice.
          </p>
        </SheetBody>
        <SheetFooter>
          <Button variant="link" size="xs">
            <Trash01 data-icon="inline-start" />
            Delete invoice
          </Button>
          <SheetFooterActions>
            <SheetClose asChild>
              <Button variant="secondary">Discard</Button>
            </SheetClose>
            <SheetClose asChild>
              <Button variant="primary">Send invoice</Button>
            </SheetClose>
          </SheetFooterActions>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { Trash01 } from "@/components/icons/trash-01";

/**
 * The footer's action group takes the free space, so an additional action
 * placed before it sits hard left while the primary pair stays right.
 */
export default function Example() {
  return (
    <Sheet>
      <SheetTrigger render={<Button variant="outline" />}>
        Edit invoice
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetHeaderContent>
            <SheetTitleGroup>
              <SheetTitle>Invoice #1042</SheetTitle>
              <SheetDescription>
                Issued 12 March 2026 · Due in 14 days
              </SheetDescription>
            </SheetTitleGroup>
            <SheetCloseButton />
          </SheetHeaderContent>
        </SheetHeader>
        <SheetBody>
          <p className="text-sm text-text-muted">
            Line items, taxes and payment history for this invoice.
          </p>
        </SheetBody>
        <SheetFooter>
          <Button variant="link" size="xs">
            <Trash01 data-icon="inline-start" />
            Delete invoice
          </Button>
          <SheetFooterActions>
            <SheetClose render={<Button variant="secondary" />}>
              Discard
            </SheetClose>
            <SheetClose render={<Button variant="primary" />}>
              Send invoice
            </SheetClose>
          </SheetFooterActions>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}

View code

Sheet · Side left

Installation

pnpm dlx velaui add @velaui/sheet
pnpm dlx velaui add @velaui/sheet

Code

sheet-side-left.tsx
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { AudioSettings01 } from "@/components/icons/audio-settings-01";

export default function Example() {
  return (
    <Sheet>
      <SheetTrigger asChild>
        <Button variant="secondary" size="sm">
          <AudioSettings01 data-icon="inline-start" />
          Filters
        </Button>
      </SheetTrigger>
      <SheetContent side="left">
        <SheetHeader>
          <SheetHeaderContent>
            <SheetTitleGroup>
              <SheetTitle>Filters</SheetTitle>
              <SheetDescription>
                Narrow the list down before you export it.
              </SheetDescription>
            </SheetTitleGroup>
            <SheetCloseButton />
          </SheetHeaderContent>
        </SheetHeader>
        <SheetBody>
          <p className="text-sm text-text-muted">
            Status, owner, date range and tags.
          </p>
        </SheetBody>
        <SheetFooter>
          <SheetFooterActions>
            <SheetClose asChild>
              <Button variant="ghost">Reset</Button>
            </SheetClose>
            <SheetClose asChild>
              <Button variant="primary">Apply filters</Button>
            </SheetClose>
          </SheetFooterActions>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { AudioSettings01 } from "@/components/icons/audio-settings-01";

export default function Example() {
  return (
    <Sheet>
      <SheetTrigger
        render={
          <Button variant="secondary" size="sm">
            <AudioSettings01 data-icon="inline-start" />
            Filters
          </Button>
        }
      />
      <SheetContent side="left">
        <SheetHeader>
          <SheetHeaderContent>
            <SheetTitleGroup>
              <SheetTitle>Filters</SheetTitle>
              <SheetDescription>
                Narrow the list down before you export it.
              </SheetDescription>
            </SheetTitleGroup>
            <SheetCloseButton />
          </SheetHeaderContent>
        </SheetHeader>
        <SheetBody>
          <p className="text-sm text-text-muted">
            Status, owner, date range and tags.
          </p>
        </SheetBody>
        <SheetFooter>
          <SheetFooterActions>
            <SheetClose render={<Button variant="ghost" />}>Reset</SheetClose>
            <SheetClose render={<Button variant="primary" />}>
              Apply filters
            </SheetClose>
          </SheetFooterActions>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}

View code

Sheet · Form

Installation

pnpm dlx velaui add @velaui/sheet
pnpm dlx velaui add @velaui/sheet

Code

sheet-form.tsx
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

export default function Example() {
  return (
    <Sheet>
      <SheetTrigger asChild>
        <Button variant="primary" size="sm">
          New member
        </Button>
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetHeaderContent>
            <SheetTitleGroup>
              <SheetTitle>Invite a member</SheetTitle>
              <SheetDescription>
                They get an email with a link that expires in 7 days.
              </SheetDescription>
            </SheetTitleGroup>
            <SheetCloseButton />
          </SheetHeaderContent>
        </SheetHeader>
        <SheetBody>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="member-name">Full name</Label>
            <Input id="member-name" size="md" placeholder="Ada Lovelace" />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="member-email">Work email</Label>
            <Input
              id="member-email"
              size="md"
              type="email"
              placeholder="ada@example.com"
            />
          </div>
        </SheetBody>
        <SheetFooter>
          <SheetFooterActions>
            <SheetClose asChild>
              <Button variant="secondary">Cancel</Button>
            </SheetClose>
            <SheetClose asChild>
              <Button variant="primary">Send invite</Button>
            </SheetClose>
          </SheetFooterActions>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

export default function Example() {
  return (
    <Sheet>
      <SheetTrigger render={<Button variant="primary" size="sm" />}>
        New member
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetHeaderContent>
            <SheetTitleGroup>
              <SheetTitle>Invite a member</SheetTitle>
              <SheetDescription>
                They get an email with a link that expires in 7 days.
              </SheetDescription>
            </SheetTitleGroup>
            <SheetCloseButton />
          </SheetHeaderContent>
        </SheetHeader>
        <SheetBody>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="member-name">Full name</Label>
            <Input id="member-name" size="md" placeholder="Ada Lovelace" />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="member-email">Work email</Label>
            <Input
              id="member-email"
              size="md"
              type="email"
              placeholder="ada@example.com"
            />
          </div>
        </SheetBody>
        <SheetFooter>
          <SheetFooterActions>
            <SheetClose render={<Button variant="secondary" />}>
              Cancel
            </SheetClose>
            <SheetClose render={<Button variant="primary" />}>
              Send invite
            </SheetClose>
          </SheetFooterActions>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}

View code

Sheet · Side bottom

Installation

pnpm dlx velaui add @velaui/sheet
pnpm dlx velaui add @velaui/sheet

Code

sheet-side-bottom.tsx
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";

/** A bottom sheet: the body sizes to its content instead of filling a column. */
export default function Example() {
  return (
    <Sheet>
      <SheetTrigger asChild>
        <Button variant="ghost">Share report</Button>
      </SheetTrigger>
      <SheetContent side="bottom">
        <SheetHeader>
          <SheetHeaderContent>
            <SheetTitleGroup>
              <SheetTitle>Share report</SheetTitle>
              <SheetDescription>
                Anyone with the link can read this report.
              </SheetDescription>
            </SheetTitleGroup>
            <SheetCloseButton />
          </SheetHeaderContent>
        </SheetHeader>
        <SheetBody className="flex-none">
          <p className="text-sm text-text-muted">
            veni.example.com/reports/q3-revenue
          </p>
        </SheetBody>
        <SheetFooter>
          <SheetFooterActions>
            <SheetClose asChild>
              <Button variant="primary">Copy link</Button>
            </SheetClose>
          </SheetFooterActions>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";

/** A bottom sheet: the body sizes to its content instead of filling a column. */
export default function Example() {
  return (
    <Sheet>
      <SheetTrigger render={<Button variant="ghost" />}>
        Share report
      </SheetTrigger>
      <SheetContent side="bottom">
        <SheetHeader>
          <SheetHeaderContent>
            <SheetTitleGroup>
              <SheetTitle>Share report</SheetTitle>
              <SheetDescription>
                Anyone with the link can read this report.
              </SheetDescription>
            </SheetTitleGroup>
            <SheetCloseButton />
          </SheetHeaderContent>
        </SheetHeader>
        <SheetBody className="flex-none">
          <p className="text-sm text-text-muted">
            veni.example.com/reports/q3-revenue
          </p>
        </SheetBody>
        <SheetFooter>
          <SheetFooterActions>
            <SheetClose render={<Button variant="primary" />}>
              Copy link
            </SheetClose>
          </SheetFooterActions>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}

Usage

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

export default function Example() {
  return (
    <Sheet>
      <SheetTrigger asChild>
        <Button variant="primary">Open panel</Button>
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetHeaderContent>
            <SheetTitleGroup>
              <SheetTitle>Master Heading</SheetTitle>
              <SheetDescription>
                A side panel slides in over the page to show details, forms or
                secondary flows without losing the current context.
              </SheetDescription>
            </SheetTitleGroup>
            <SheetCloseButton />
          </SheetHeaderContent>
        </SheetHeader>
        <SheetBody>
          <p className="text-sm text-text-muted">
            Everything the panel has to say goes here. The body scrolls on its
            own, so the heading and the actions stay put.
          </p>
        </SheetBody>
        <SheetFooter>
          <SheetFooterActions>
            <SheetClose asChild>
              <Button variant="secondary">Cancel</Button>
            </SheetClose>
            <SheetClose asChild>
              <Button variant="primary">Save changes</Button>
            </SheetClose>
          </SheetFooterActions>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";

export default function Example() {
  return (
    <Sheet>
      <SheetTrigger render={<Button variant="primary" />}>
        Open panel
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetHeaderContent>
            <SheetTitleGroup>
              <SheetTitle>Master Heading</SheetTitle>
              <SheetDescription>
                A side panel slides in over the page to show details, forms or
                secondary flows without losing the current context.
              </SheetDescription>
            </SheetTitleGroup>
            <SheetCloseButton />
          </SheetHeaderContent>
        </SheetHeader>
        <SheetBody>
          <p className="text-sm text-text-muted">
            Everything the panel has to say goes here. The body scrolls on its
            own, so the heading and the actions stay put.
          </p>
        </SheetBody>
        <SheetFooter>
          <SheetFooterActions>
            <SheetClose render={<Button variant="secondary" />}>
              Cancel
            </SheetClose>
            <SheetClose render={<Button variant="primary" />}>
              Save changes
            </SheetClose>
          </SheetFooterActions>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}

Props

Sheet

Takes every prop of SheetProps.

SheetBody

Takes every prop of SheetBodyProps.

SheetClose

Takes every prop of SheetCloseProps.

SheetCloseButton

Takes every prop of SheetCloseButtonProps.

SheetContent

PropTypeDefault
side"top" | "bottom" | "left" | "right""right"

Takes these and every prop of SheetContentProps.

SheetDescription

Takes every prop of SheetDescriptionProps.

SheetFooter

Takes every prop of SheetFooterProps.

SheetFooterActions

Takes every prop of SheetFooterActionsProps.

SheetHeader

Takes every prop of SheetHeaderProps.

SheetHeaderContent

Takes every prop of SheetHeaderContentProps.

SheetOverlay

Takes every prop of SheetOverlayProps.

SheetPortal

Takes every prop of SheetPortalProps.

SheetTitle

Takes every prop of SheetTitleProps.

SheetTitleGroup

Takes every prop of SheetTitleGroupProps.

SheetTrigger

Takes every prop of SheetTriggerProps.

Dependencies

npm
radix-ui@1.6.0
Installs with it
1 icon
npm
@base-ui/react@1.6.0
Installs with it
1 icon

Source

components/ui/sheet.tsx
import * as React from "react";
import { Dialog as SheetPrimitive } from "radix-ui";
import { Close02 } from "@/components/icons/close-02";

import { cn } from "@/lib/utils";

export type SheetProps = React.ComponentProps<typeof SheetPrimitive.Root>;

function Sheet({ ...props }: SheetProps) {
  return <SheetPrimitive.Root data-slot="sheet" {...props} />;
}

export type SheetTriggerProps = React.ComponentProps<typeof SheetPrimitive.Trigger>;

function SheetTrigger({
  ...props
}: SheetTriggerProps) {
  return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
}

export type SheetCloseProps = React.ComponentProps<typeof SheetPrimitive.Close>;

function SheetClose({
  ...props
}: SheetCloseProps) {
  return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
}

export type SheetPortalProps = React.ComponentProps<typeof SheetPrimitive.Portal>;

function SheetPortal({
  ...props
}: SheetPortalProps) {
  return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
}

export type SheetOverlayProps = React.ComponentProps<typeof SheetPrimitive.Overlay>;

function SheetOverlay({
  className,
  ...props
}: SheetOverlayProps) {
  return (
    <SheetPrimitive.Overlay
      data-slot="sheet-overlay"
      className={cn(
        "fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
        className,
      )}
      {...props}
    />
  );
}

export type SheetContentProps = React.ComponentProps<typeof SheetPrimitive.Content> & {
  side?: "top" | "right" | "bottom" | "left";
};

/**
 * The panel floats: it carries a full border, a 24px radius on every corner and
 * a bottom shadow, so it sits inset from the viewport edge rather than pinned
 * flush to it. Horizontal padding lives here; every band supplies its own
 * vertical padding.
 */
function SheetContent({
  className,
  children,
  side = "right",
  ...props
}: SheetContentProps) {
  return (
    <SheetPortal>
      <SheetOverlay />
      <SheetPrimitive.Content
        data-slot="sheet-content"
        data-side={side}
        className={cn(
          "fixed z-50 flex flex-col overflow-hidden rounded-3xl border border-border-default bg-bg-main px-6 text-sm text-text-strong shadow-xs transition duration-200 ease-in-out outline-none",
          "data-[side=right]:inset-y-4 data-[side=right]:right-4 data-[side=right]:w-3/4 data-[side=right]:sm:max-w-[605px]",
          "data-[side=left]:inset-y-4 data-[side=left]:left-4 data-[side=left]:w-3/4 data-[side=left]:sm:max-w-[605px]",
          "data-[side=top]:inset-x-4 data-[side=top]:top-4 data-[side=top]:h-auto",
          "data-[side=bottom]:inset-x-4 data-[side=bottom]:bottom-4 data-[side=bottom]:h-auto",
          "data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10",
          "data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10",
          className,
        )}
        {...props}
      >
        {children}
      </SheetPrimitive.Content>
    </SheetPortal>
  );
}

export type SheetHeaderProps = React.ComponentProps<"div">;

/** The whole header band — the title block, and whatever follows it (tabs). */
function SheetHeader({ className, ...props }: SheetHeaderProps) {
  return (
    <div
      data-slot="sheet-header"
      className={cn("flex w-full flex-col gap-8 pt-6", className)}
      {...props}
    />
  );
}

export type SheetHeaderContentProps = React.ComponentProps<"div">;

/**
 * The title block plus anything attached to it (a badge), and the anchor for
 * the close button. Sits above the header's own gap, so tabs placed as its
 * sibling clear it by 32px.
 */
function SheetHeaderContent({
  className,
  ...props
}: SheetHeaderContentProps) {
  return (
    <div
      data-slot="sheet-header-content"
      className={cn("relative flex w-full flex-col gap-2", className)}
      {...props}
    />
  );
}

export type SheetTitleGroupProps = React.ComponentProps<"div">;

/** Title and description, kept clear of the close button by the right padding. */
function SheetTitleGroup({ className, ...props }: SheetTitleGroupProps) {
  return (
    <div
      data-slot="sheet-title-group"
      className={cn("flex w-full flex-col gap-1 pr-10", className)}
      {...props}
    />
  );
}

export type SheetCloseButtonProps = React.ComponentProps<typeof SheetPrimitive.Close>;

/** The bare 16px dismiss glyph pinned to the top-right of the title block. */
function SheetCloseButton({
  className,
  ...props
}: SheetCloseButtonProps) {
  return (
    <SheetPrimitive.Close
      data-slot="sheet-close"
      className={cn(
        "absolute top-0 right-0 inline-flex size-4 items-center justify-center rounded-xs text-icon-strong transition-colors focus-visible:ring-3 focus-visible:ring-ring-neutral-shadow focus-visible:outline-none [&_svg]:size-4",
        className,
      )}
      {...props}
    >
      <Close02 />
      <span className="sr-only">Close</span>
    </SheetPrimitive.Close>
  );
}

export type SheetBodyProps = React.ComponentProps<"div">;

/** The scrolling middle band. */
function SheetBody({ className, ...props }: SheetBodyProps) {
  return (
    <div
      data-slot="sheet-body"
      className={cn(
        "flex w-full min-h-0 flex-1 flex-col gap-4 overflow-y-auto py-8",
        className,
      )}
      {...props}
    />
  );
}

export type SheetFooterProps = React.ComponentProps<"div">;

function SheetFooter({ className, ...props }: SheetFooterProps) {
  return (
    <div
      data-slot="sheet-footer"
      className={cn(
        "flex w-full items-center justify-end gap-4 py-6",
        className,
      )}
      {...props}
    />
  );
}

export type SheetFooterActionsProps = React.ComponentProps<"div">;

/**
 * The right-aligned action group. Taking the free space is what pushes an
 * additional action placed before it to the far left of the footer.
 */
function SheetFooterActions({
  className,
  ...props
}: SheetFooterActionsProps) {
  return (
    <div
      data-slot="sheet-footer-actions"
      className={cn(
        "flex min-w-0 flex-1 items-center justify-end gap-2",
        className,
      )}
      {...props}
    />
  );
}

export type SheetTitleProps = React.ComponentProps<typeof SheetPrimitive.Title>;

function SheetTitle({
  className,
  ...props
}: SheetTitleProps) {
  return (
    <SheetPrimitive.Title
      data-slot="sheet-title"
      className={cn(
        "text-3xl leading-[1.2] font-semibold text-text-strong",
        className,
      )}
      {...props}
    />
  );
}

export type SheetDescriptionProps = React.ComponentProps<typeof SheetPrimitive.Description>;

function SheetDescription({
  className,
  ...props
}: SheetDescriptionProps) {
  return (
    <SheetPrimitive.Description
      data-slot="sheet-description"
      className={cn("text-sm leading-[1.4] text-text-muted", className)}
      {...props}
    />
  );
}

export {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetOverlay,
  SheetPortal,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
};
components/ui/sheet.tsx
import * as React from "react";
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
import { Close02 } from "@/components/icons/close-02";

import { cn } from "@/lib/utils";

export type SheetProps = SheetPrimitive.Root.Props;

function Sheet({ ...props }: SheetProps) {
  return <SheetPrimitive.Root data-slot="sheet" {...props} />;
}

export type SheetTriggerProps = SheetPrimitive.Trigger.Props;

function SheetTrigger({ ...props }: SheetTriggerProps) {
  return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
}

export type SheetCloseProps = SheetPrimitive.Close.Props;

function SheetClose({ ...props }: SheetCloseProps) {
  return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
}

export type SheetPortalProps = SheetPrimitive.Portal.Props;

function SheetPortal({ ...props }: SheetPortalProps) {
  return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
}

export type SheetOverlayProps = SheetPrimitive.Backdrop.Props;

function SheetOverlay({
  className,
  ...props
}: SheetOverlayProps) {
  return (
    <SheetPrimitive.Backdrop
      data-slot="sheet-overlay"
      className={cn(
        "fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
        className,
      )}
      {...props}
    />
  );
}

export type SheetContentProps = SheetPrimitive.Popup.Props & {
  side?: "top" | "right" | "bottom" | "left";
};

/**
 * The panel floats: it carries a full border, a 24px radius on every corner and
 * a bottom shadow, so it sits inset from the viewport edge rather than pinned
 * flush to it. Horizontal padding lives here; every band supplies its own
 * vertical padding.
 */
function SheetContent({
  className,
  children,
  side = "right",
  ...props
}: SheetContentProps) {
  return (
    <SheetPortal>
      <SheetOverlay />
      <SheetPrimitive.Popup
        data-slot="sheet-content"
        data-side={side}
        className={cn(
          "fixed z-50 flex flex-col overflow-hidden rounded-3xl border border-border-default bg-bg-main px-6 text-sm text-text-strong shadow-xs transition duration-200 ease-in-out outline-none",
          "data-[side=right]:inset-y-4 data-[side=right]:right-4 data-[side=right]:w-3/4 data-[side=right]:sm:max-w-[605px]",
          "data-[side=left]:inset-y-4 data-[side=left]:left-4 data-[side=left]:w-3/4 data-[side=left]:sm:max-w-[605px]",
          "data-[side=top]:inset-x-4 data-[side=top]:top-4 data-[side=top]:h-auto",
          "data-[side=bottom]:inset-x-4 data-[side=bottom]:bottom-4 data-[side=bottom]:h-auto",
          "data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10",
          "data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10",
          className,
        )}
        {...props}
      >
        {children}
      </SheetPrimitive.Popup>
    </SheetPortal>
  );
}

export type SheetHeaderProps = React.ComponentProps<"div">;

/** The whole header band — the title block, and whatever follows it (tabs). */
function SheetHeader({ className, ...props }: SheetHeaderProps) {
  return (
    <div
      data-slot="sheet-header"
      className={cn("flex w-full flex-col gap-8 pt-6", className)}
      {...props}
    />
  );
}

export type SheetHeaderContentProps = React.ComponentProps<"div">;

/**
 * The title block plus anything attached to it (a badge), and the anchor for
 * the close button. Sits above the header's own gap, so tabs placed as its
 * sibling clear it by 32px.
 */
function SheetHeaderContent({
  className,
  ...props
}: SheetHeaderContentProps) {
  return (
    <div
      data-slot="sheet-header-content"
      className={cn("relative flex w-full flex-col gap-2", className)}
      {...props}
    />
  );
}

export type SheetTitleGroupProps = React.ComponentProps<"div">;

/** Title and description, kept clear of the close button by the right padding. */
function SheetTitleGroup({ className, ...props }: SheetTitleGroupProps) {
  return (
    <div
      data-slot="sheet-title-group"
      className={cn("flex w-full flex-col gap-1 pr-10", className)}
      {...props}
    />
  );
}

export type SheetCloseButtonProps = SheetPrimitive.Close.Props;

/** The bare 16px dismiss glyph pinned to the top-right of the title block. */
function SheetCloseButton({
  className,
  ...props
}: SheetCloseButtonProps) {
  return (
    <SheetPrimitive.Close
      data-slot="sheet-close"
      className={cn(
        "absolute top-0 right-0 inline-flex size-4 items-center justify-center rounded-xs text-icon-strong transition-colors focus-visible:ring-3 focus-visible:ring-ring-neutral-shadow focus-visible:outline-none [&_svg]:size-4",
        className,
      )}
      {...props}
    >
      <Close02 />
      <span className="sr-only">Close</span>
    </SheetPrimitive.Close>
  );
}

export type SheetBodyProps = React.ComponentProps<"div">;

/** The scrolling middle band. */
function SheetBody({ className, ...props }: SheetBodyProps) {
  return (
    <div
      data-slot="sheet-body"
      className={cn(
        "flex w-full min-h-0 flex-1 flex-col gap-4 overflow-y-auto py-8",
        className,
      )}
      {...props}
    />
  );
}

export type SheetFooterProps = React.ComponentProps<"div">;

function SheetFooter({ className, ...props }: SheetFooterProps) {
  return (
    <div
      data-slot="sheet-footer"
      className={cn(
        "flex w-full items-center justify-end gap-4 py-6",
        className,
      )}
      {...props}
    />
  );
}

export type SheetFooterActionsProps = React.ComponentProps<"div">;

/**
 * The right-aligned action group. Taking the free space is what pushes an
 * additional action placed before it to the far left of the footer.
 */
function SheetFooterActions({
  className,
  ...props
}: SheetFooterActionsProps) {
  return (
    <div
      data-slot="sheet-footer-actions"
      className={cn(
        "flex min-w-0 flex-1 items-center justify-end gap-2",
        className,
      )}
      {...props}
    />
  );
}

export type SheetTitleProps = SheetPrimitive.Title.Props;

function SheetTitle({ className, ...props }: SheetTitleProps) {
  return (
    <SheetPrimitive.Title
      data-slot="sheet-title"
      className={cn(
        "text-3xl leading-[1.2] font-semibold text-text-strong",
        className,
      )}
      {...props}
    />
  );
}

export type SheetDescriptionProps = SheetPrimitive.Description.Props;

function SheetDescription({
  className,
  ...props
}: SheetDescriptionProps) {
  return (
    <SheetPrimitive.Description
      data-slot="sheet-description"
      className={cn("text-sm leading-[1.4] text-text-muted", className)}
      {...props}
    />
  );
}

export {
  Sheet,
  SheetBody,
  SheetClose,
  SheetCloseButton,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetFooterActions,
  SheetHeader,
  SheetHeaderContent,
  SheetOverlay,
  SheetPortal,
  SheetTitle,
  SheetTitleGroup,
  SheetTrigger,
};