Innovations
modals /

Sheet / Slide-in Drawer

Bottom sheet on mobile, side panel on desktop. Built with vaul for native-feeling drag interactions.

Preview

Source

tsx
"use client";

import { Drawer } from "vaul";
import { Button } from "@/components/ui/button";
import {
  Home,
  BarChart2,
  Settings,
  Users,
  HelpCircle,
  LogOut,
  Menu,
} from "lucide-react";

const navItems = [
  { icon: Home, label: "Dashboard", href: "#" },
  { icon: BarChart2, label: "Analytics", href: "#" },
  { icon: Users, label: "Team", href: "#" },
  { icon: Settings, label: "Settings", href: "#" },
  { icon: HelpCircle, label: "Help & Support", href: "#" },
];

export default function SheetDrawer() {
  return (
    <div className="flex min-h-[200px] items-center justify-center p-8">
      <Drawer.Root>
        <Drawer.Trigger asChild>
          <Button variant="outline" size="lg" className="gap-2">
            <Menu className="h-5 w-5" />
            Open navigation panel
          </Button>
        </Drawer.Trigger>

        <Drawer.Portal>
          <Drawer.Overlay className="fixed inset-0 z-40 bg-black/50" />
          <Drawer.Content className="fixed bottom-0 left-0 right-0 z-50 flex flex-col rounded-t-2xl border-t border-border bg-background focus:outline-none sm:bottom-auto sm:left-auto sm:right-0 sm:top-0 sm:h-full sm:w-72 sm:rounded-none sm:rounded-l-2xl sm:border-t-0 sm:border-l">
            {/* Drag handle — visible on mobile only */}
            <div className="mx-auto mt-3 h-1.5 w-12 flex-shrink-0 rounded-full bg-border sm:hidden" />

            <div className="flex flex-1 flex-col p-6 overflow-y-auto">
              {/* Header */}
              <div className="mb-6">
                <p className="text-xs font-semibold uppercase tracking-widest text-muted-foreground mb-1">
                  Navigation
                </p>
                <h2 className="text-lg font-bold text-foreground">My Workspace</h2>
              </div>

              {/* Nav items */}
              <nav className="flex-1 space-y-1">
                {navItems.map(({ icon: Icon, label, href }) => (
                  <a
                    key={label}
                    href={href}
                    className="flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-foreground transition-colors hover:bg-muted hover:text-primary"
                  >
                    <Icon className="h-4 w-4 text-muted-foreground shrink-0" />
                    {label}
                  </a>
                ))}
              </nav>

              {/* Footer */}
              <div className="pt-6 border-t border-border mt-6">
                <a
                  href="#"
                  className="flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-red-500 hover:bg-red-500/10 transition-colors"
                >
                  <LogOut className="h-4 w-4 shrink-0" />
                  Sign out
                </a>
              </div>
            </div>
          </Drawer.Content>
        </Drawer.Portal>
      </Drawer.Root>
    </div>
  );
}
Claude Code Instructions

CLI Install

npx innovations add sheet-drawer

Where to use it

Use this for mobile navigation menus, slide-in filter panels, or detail sidebars. The vaul Drawer is a bottom sheet on mobile and automatically adapts to a side panel on sm+ breakpoints via CSS positioning. import { Drawer } from 'vaul'; // <Drawer.Root> <Drawer.Trigger> <Drawer.Portal><Drawer.Overlay /><Drawer.Content>...</Drawer.Content></Drawer.Portal></Drawer.Root> Add to your root layout or page component — the Portal renders outside the React tree so it always appears above all content.