Sheet

Edge-anchored panel built on the Dialog primitive — slides in flush against a viewport edge with a fading backdrop, unlike Dialog's detached floating plane.

Standalone

A left-anchored sheet showing a proximity-hover nav list.

standalone

Side

The same sheet anchored to each edge, with content shaped for its position — a banner up top, settings on the right, an icon-row action sheet on the bottom, and the same nav list as Standalone on the left.

side

Install

Install the packages this component imports.

bun add @base-ui/react
Terminal

Also requires lib/utils.ts (see Installation).

Source

Copy and paste the following code into your project.

"use client";

import type { ComponentProps } from "react";
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";

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

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

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

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

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

function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
  return (
    <SheetPrimitive.Backdrop
      data-slot="sheet-overlay"
      className={cn(
        "fixed inset-0 z-50 bg-black/40 backdrop-blur-sm transition-opacity duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0",
        className,
      )}
      {...props}
    />
  );
}

/**
 * Sits flush against the edge it slides in from — only the leading edge
 * (the one opposite where it attaches) is rounded, `shadow-panel` for
 * depth. A full `rounded-3xl` island reads as floating, detached from the
 * viewport; this instead reads as a panel that's slid out from just off-
 * screen, so three of its four sides touch the browser edge like the
 * viewport itself does.
 */
function SheetContent({
  className,
  children,
  side = "left",
  ...props
}: SheetPrimitive.Popup.Props & {
  side?: "top" | "right" | "bottom" | "left";
}) {
  return (
    <SheetPortal>
      <SheetOverlay />
      <SheetPrimitive.Popup
        data-slot="sheet-content"
        data-side={side}
        className={cn(
          "fixed z-50 flex flex-col overflow-hidden bg-background shadow-panel transition-transform duration-200 ease-out",
          "data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:w-64 data-[side=left]:max-w-[85vw] data-[side=left]:rounded-r-3xl",
          "data-[side=left]:data-starting-style:-translate-x-full data-[side=left]:data-ending-style:-translate-x-full",
          "data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:w-64 data-[side=right]:max-w-[85vw] data-[side=right]:rounded-l-3xl",
          "data-[side=right]:data-starting-style:translate-x-full data-[side=right]:data-ending-style:translate-x-full",
          "data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:rounded-b-3xl",
          "data-[side=top]:data-starting-style:-translate-y-full data-[side=top]:data-ending-style:-translate-y-full",
          "data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:rounded-t-3xl",
          "data-[side=bottom]:data-starting-style:translate-y-full data-[side=bottom]:data-ending-style:translate-y-full",
          className,
        )}
        {...props}
      >
        {children}
      </SheetPrimitive.Popup>
    </SheetPortal>
  );
}

function SheetHeader({ className, ...props }: ComponentProps<"div">) {
  return (
    <div
      data-slot="sheet-header"
      className={cn("flex h-14 shrink-0 items-center border-b border-border px-6", className)}
      {...props}
    />
  );
}

function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
  return (
    <SheetPrimitive.Title
      data-slot="sheet-title"
      className={cn("text-caption font-semibold tracking-tight text-foreground", className)}
      {...props}
    />
  );
}

function SheetDescription({ className, ...props }: SheetPrimitive.Description.Props) {
  return (
    <SheetPrimitive.Description
      data-slot="sheet-description"
      className={cn("text-caption text-muted-foreground", className)}
      {...props}
    />
  );
}

export {
  Sheet,
  SheetTrigger,
  SheetClose,
  SheetPortal,
  SheetOverlay,
  SheetContent,
  SheetHeader,
  SheetTitle,
  SheetDescription,
};
sheet.tsx

API Reference

Sheet
defaultOpenfalse

boolean

Whether the sheet is initially open, uncontrolled.

open

boolean

Controlled open state.

onOpenChange

(open: boolean) => void

Called when the open state changes.

modaltrue

boolean | "trap-focus"

Whether the rest of the page is inert while the sheet is open.

API Reference

SheetContent
side"left"

"top" | "right" | "bottom" | "left"

Which viewport edge the sheet slides in from.

children

ReactNode

Sheet content — typically SheetHeader and body content.

API Reference

SheetTitleSheetDescription
children

ReactNode

Title or description content.