codefast/ui

Command Palette

Search for a command to run...

Source
Overlay

Dialog

Modal window with focus trap, backdrop blur, and accessible close. Use AlertDialog for destructive confirms.

Examples

Custom Close Button

Replace the default close control with your own button.

42 lines
import { Button } from "@codefast/ui/button";
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@codefast/ui/dialog";
import { Input } from "@codefast/ui/input";
import { Label } from "@codefast/ui/label";

export function DialogCloseButton() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="outline">Share</Button>
      </DialogTrigger>
      <DialogContent className="sm:max-w-md">
        <DialogHeader>
          <DialogTitle>Share link</DialogTitle>
          <DialogDescription>Anyone who has this link will be able to view this.</DialogDescription>
        </DialogHeader>
        <div className="flex items-center gap-2">
          <div className="grid flex-1 gap-2">
            <Label htmlFor="link" className="sr-only">
              Link
            </Label>
            <Input id="link" defaultValue="https://codefastlabs.com/docs/installation" readOnly />
          </div>
        </div>
        <DialogFooter className="sm:justify-start">
          <DialogClose asChild>
            <Button type="button">Close</Button>
          </DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

No Close Button

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

25 lines
import { Button } from "@codefast/ui/button";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@codefast/ui/dialog";

export function DialogNoCloseButton() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="outline">No Close Button</Button>
      </DialogTrigger>
      <DialogContent showCloseButton={false}>
        <DialogHeader>
          <DialogTitle>No Close Button</DialogTitle>
          <DialogDescription>This dialog doesn&apos;t have a close button in the top-right corner.</DialogDescription>
        </DialogHeader>
      </DialogContent>
    </Dialog>
  );
}

RTL

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

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

92 lines
import { Button } from "@codefast/ui/button";
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@codefast/ui/dialog";
import { Field, FieldGroup } from "@codefast/ui/field";
import { Input } from "@codefast/ui/input";
import { Label } from "@codefast/ui/label";

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: {
      openDialog: "Open Dialog",
      editProfile: "Edit profile",
      description: "Make changes to your profile here. Click save when you're done.",
      name: "Name",
      username: "Username",
      cancel: "Cancel",
      saveChanges: "Save changes",
    },
  },
  ar: {
    dir: "rtl",
    values: {
      openDialog: "فتح الحوار",
      editProfile: "تعديل الملف الشخصي",
      description: "قم بإجراء تغييرات على ملفك الشخصي هنا. انقر فوق حفظ عند الانتهاء.",
      name: "الاسم",
      username: "اسم المستخدم",
      cancel: "إلغاء",
      saveChanges: "حفظ التغييرات",
    },
  },
  he: {
    dir: "rtl",
    values: {
      openDialog: "פתח דיאלוג",
      editProfile: "ערוך פרופיל",
      description: "בצע שינויים בפרופיל שלך כאן. לחץ על שמור כשתסיים.",
      name: "שם",
      username: "שם משתמש",
      cancel: "בטל",
      saveChanges: "שמור שינויים",
    },
  },
};

export function DialogRtl() {
  const { dir, t, language } = useTranslation(translations, "ar");

  return (
    <Dialog>
      <form>
        <DialogTrigger asChild>
          <Button variant="outline">{t.openDialog}</Button>
        </DialogTrigger>
        <DialogContent className="sm:max-w-sm" dir={dir} data-lang={dir === "rtl" ? language : undefined}>
          <DialogHeader>
            <DialogTitle>{t.editProfile}</DialogTitle>
            <DialogDescription>{t.description}</DialogDescription>
          </DialogHeader>
          <FieldGroup>
            <Field>
              <Label htmlFor="name-1">{t.name}</Label>
              <Input id="name-1" name="name" defaultValue="Pedro Duarte" />
            </Field>
            <Field>
              <Label htmlFor="username-1">{t.username}</Label>
              <Input id="username-1" name="username" defaultValue="@vuongphan" />
            </Field>
          </FieldGroup>
          <DialogFooter>
            <DialogClose asChild>
              <Button variant="outline">{t.cancel}</Button>
            </DialogClose>
            <Button type="submit">{t.saveChanges}</Button>
          </DialogFooter>
        </DialogContent>
      </form>
    </Dialog>
  );
}

Scrollable Content

Long content can scroll while the header stays in view.

36 lines
import { Button } from "@codefast/ui/button";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@codefast/ui/dialog";

export function DialogScrollableContent() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="outline">Scrollable Content</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Scrollable Content</DialogTitle>
          <DialogDescription>This is a dialog with scrollable content.</DialogDescription>
        </DialogHeader>
        <div className="no-scrollbar -mx-4 max-h-[50vh] overflow-y-auto px-4">
          {Array.from({ length: 10 }).map((_, index) => (
            <p key={index} className="mb-4 leading-normal">
              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>
      </DialogContent>
    </Dialog>
  );
}

Usage

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

33 lines
import { Button } from "@codefast/ui/button";
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@codefast/ui/dialog";

export function DialogUsage() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="outline">Edit profile</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Edit profile</DialogTitle>
          <DialogDescription>Make changes to your profile here.</DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <DialogClose asChild>
            <Button variant="outline">Cancel</Button>
          </DialogClose>
          <Button>Save changes</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

Anatomy

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

Dialog
├── DialogTrigger
└── DialogContent
├── DialogHeader
│ ├── DialogTitle
│ └── DialogDescription
├── DialogBody
└── DialogFooter
└── DialogClose

Features

  • DialogBody is a codefast addition over upstream Radix — long content scrolls on its own while DialogHeader/DialogFooter stay pinned, instead of the whole panel scrolling.
  • DialogFooter's showCloseButton renders a ready-made "Close" button — no need to wire DialogClose asChild yourself.
  • DialogContent's showCloseButton (default true) toggles the built-in corner × button.
  • modal={false} lets content outside the dialog stay interactive instead of becoming inert.

API reference

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

Dialog

Root. Manages open state.

openboolean

The controlled open state.

defaultOpenboolean

The open state when initially rendered (uncontrolled).

Defaultfalse

onOpenChange(open: boolean) => void

Called when the open state changes.

modalboolean

When true, content outside the dialog is inert.

Defaulttrue

DialogContent

The panel. Traps focus and renders in a portal.

onEscapeKeyDown(event) => void

Intercept the Escape-to-close behaviour.

Accessibility

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

KeyFunction
SpaceWhen the trigger is focused, opens the dialog.
EnterWhen the trigger is focused, opens the dialog.
TabCycles focus within the dialog (focus is trapped).
Shift+TabCycles focus backwards within the dialog.
EscCloses the dialog and restores focus to the trigger.
  • Focus moves into the dialog on open and returns to the trigger on close.
  • DialogTitle and DialogDescription are wired to aria-labelledby / aria-describedby.
  • While open, the rest of the page is marked inert for assistive tech.

Guidelines

Conventions that keep usage consistent across an app.

Do

  • Always include a DialogTitle, even if visually concise.
  • Use a Dialog for focused tasks; use AlertDialog for destructive confirmations.

Don’t

  • Don’t stack multiple dialogs on top of one another.
  • Don’t put long, primary content in a dialog — use a page or a sheet instead.

Explore further

Ready to integrate?

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