codefast/ui

Command Palette

Search for a command to run...

Source
Overlay

Sheet

Side-anchored panel (left, right, top, or bottom). Useful for settings and detail drawers.

Examples

No Close Button

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

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

export function SheetNoCloseButton() {
  return (
    <Sheet>
      <SheetTrigger asChild>
        <Button variant="outline">Open Sheet</Button>
      </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

Right-to-left layout support for languages such as Arabic and Hebrew.

Translations are AI-generated for demonstration and may be imperfect.

89 lines
import { Button } from "@codefast/ui/button";
import { Field, FieldGroup, FieldLabel } from "@codefast/ui/field";
import { Input } from "@codefast/ui/input";
import {
  Sheet,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@codefast/ui/sheet";

import type { Translations } from "#/features/components-catalog/components/detail/language";
import { useTranslation } from "#/features/components-catalog/components/detail/language-context";

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 asChild>
        <Button variant="outline">{t.open}</Button>
      </SheetTrigger>
      <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 id="sheet-rtl-name" defaultValue="Pedro Duarte" />
          </Field>
          <Field>
            <FieldLabel htmlFor="sheet-rtl-username">{t.username}</FieldLabel>
            <Input id="sheet-rtl-username" defaultValue="peduarte" />
          </Field>
        </FieldGroup>
        <SheetFooter>
          <Button type="submit">{t.save}</Button>
          <SheetClose asChild>
            <Button variant="outline">{t.close}</Button>
          </SheetClose>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}

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.

52 lines
import { Button } from "@codefast/ui/button";
import {
  Sheet,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@codefast/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 asChild>
            <Button variant="outline" className="capitalize">
              {side}
            </Button>
          </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 asChild>
                <Button variant="outline">Cancel</Button>
              </SheetClose>
            </SheetFooter>
          </SheetContent>
        </Sheet>
      ))}
    </div>
  );
}

Usage

The minimal import and composition — see Examples below for styled, real-world variants.

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

export function SheetUsage() {
  return (
    <Sheet>
      <SheetTrigger asChild>
        <Button variant="outline">Edit profile</Button>
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetTitle>Edit profile</SheetTitle>
          <SheetDescription>Make changes to your profile here.</SheetDescription>
        </SheetHeader>
        <SheetFooter>
          <SheetClose asChild>
            <Button variant="outline">Cancel</Button>
          </SheetClose>
          <Button>Save changes</Button>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  );
}

Anatomy

How the parts nest — every slot the component exposes, in composition order.

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

Features

  • SheetBody is a codefast addition over upstream Radix — long content scrolls on its own while Header/Footer stay pinned, same as DialogBody.
  • Four slide-in edges via side ("top" | "right" | "bottom" | "left", default "right").
  • Built on Dialog under the hood — inherits its focus trap and inert-background behaviour.

API reference

Props for each part of the component. All native element props are also forwarded.

Sheet

Root. Manages open state (a Dialog under the hood).

openboolean

The controlled open state.

defaultOpenboolean

The open state when initially rendered (uncontrolled).

Defaultfalse

onOpenChange(open: boolean) => void

Called when the open state changes.

SheetContent

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

Which edge the panel slides in from.

Default"right"

Accessibility

Built to be keyboard-navigable and screen-reader friendly out of the box.

KeyFunction
SpaceOpens the sheet when the trigger is focused.
TabCycles focus within the sheet (focus is trapped).
EscCloses the sheet and restores focus to the trigger.
  • A Sheet is a Dialog: focus is trapped and the background is inert while open.
  • Always provide a SheetTitle so the panel has an accessible name.
  • Use a Sheet for side tasks; use a Drawer (Vaul) for mobile bottom-sheet gestures.

Guidelines

Conventions that keep usage consistent across an app.

Do

  • Use for secondary tasks — filters, details, quick edits.
  • Anchor navigation sheets to the left, content/detail sheets to the right.

Don’t

  • Don’t put a multi-step primary flow in a sheet — use a page.
  • Don’t stack a sheet over a dialog.

Explore further

Ready to integrate?

Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.