Overlays

Sheet.

A dialog that slides in from an edge of the screen, for settings panels and mobile navigation.

Installation#

With the CLI

$npx lanterncn add sheet

This also installs the Lantern theme and any Lantern UI components it depends on. The shadcn CLI works directly too: npx shadcn@latest add httptim/lantern-ui/sheet. See Installation if your project is not set up yet.

Manually

  1. Install the dependencies

    npm install radix-ui lucide-react
  2. Copy the source into your project

    components/ui/sheet.tsx
    "use client";
    
    import * as React from "react";
    import { Dialog as SheetPrimitive } from "radix-ui";
    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 SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>) {
      return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
    }
    
    function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
      return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
    }
    
    function SheetOverlay({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
      return (
        <SheetPrimitive.Overlay
          data-slot="sheet-overlay"
          className={cn(
            "fixed inset-0 z-50 bg-[#080b0a]/75 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
            className,
          )}
          {...props}
        />
      );
    }
    
    function SheetContent({
      className,
      children,
      side = "right",
      showCloseButton = true,
      ...props
    }: React.ComponentProps<typeof SheetPrimitive.Content> & {
      side?: "top" | "right" | "bottom" | "left";
      showCloseButton?: boolean;
    }) {
      return (
        <SheetPortal>
          <SheetOverlay />
          <SheetPrimitive.Content
            data-slot="sheet-content"
            data-side={side}
            className={cn(
              "fixed z-50 flex flex-col gap-4 overflow-y-auto bg-popover text-popover-foreground outline-none transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-200 data-[state=open]:animate-in data-[state=open]:duration-300",
              side === "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",
              side === "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",
              side === "top" &&
                "inset-x-0 top-0 h-auto max-h-[85dvh] border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
              side === "bottom" &&
                "inset-x-0 bottom-0 h-auto max-h-[85dvh] border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
              className,
            )}
            {...props}
          >
            {children}
            {showCloseButton && (
              <SheetPrimitive.Close
                data-slot="sheet-close"
                className="absolute top-4 right-4 inline-flex size-7 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors outline-none hover:bg-secondary hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-popover disabled:pointer-events-none [&_svg]:size-4"
              >
                <XIcon />
                <span className="sr-only">Close</span>
              </SheetPrimitive.Close>
            )}
          </SheetPrimitive.Content>
        </SheetPortal>
      );
    }
    
    function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
      return <div data-slot="sheet-header" className={cn("flex flex-col gap-1.5 p-5 pr-12", 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-5", className)} {...props} />;
    }
    
    function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
      return (
        <SheetPrimitive.Title
          data-slot="sheet-title"
          className={cn("font-display text-lg leading-tight font-medium tracking-tight", className)}
          {...props}
        />
      );
    }
    
    function SheetDescription({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Description>) {
      return (
        <SheetPrimitive.Description
          data-slot="sheet-description"
          className={cn("text-sm text-muted-foreground", className)}
          {...props}
        />
      );
    }
    
    export { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger };
  3. Update the import paths to match your project

    The source imports cn from @/lib/utils.

Usage#

import {
  Sheet,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet";
<Sheet>
  <SheetTrigger asChild>
    <Button>Open</Button>
  </SheetTrigger>
  <SheetContent side="right">
    <SheetHeader>
      <SheetTitle>Server settings</SheetTitle>
      <SheetDescription>Where your hub is hosted.</SheetDescription>
    </SheetHeader>
  </SheetContent>
</Sheet>

Set side to top, right, bottom or left. Left and right sheets take three quarters of the screen on phones and cap at 24rem on larger screens.

Examples#

Sides