Westy Design Systemv0.2.7
OverviewComponentsExamplesStorybook

Foundations

  • Typography
  • Colors
  • Spacing
  • Radius
  • Shadows
  • Icons

Components

  • Accordion
  • Alert
  • Alert Dialog
  • Aspect Ratio
  • Avatar
  • Badge
  • Breadcrumb
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Data Table
  • Date Picker
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • Field
  • Hover Card
  • Input
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Label
  • Menubar
  • Native Select
  • Navigation Menu
  • Pagination
  • Popover
  • Progress
  • Questionnaire
  • Radio Group
  • Resizable
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Skeleton
  • Slider
  • Sonner
  • Spinner
  • Switch
  • Table
  • Tabs
  • Textarea
  • Toggle
  • Toggle Group
  • Tooltip

Chat

  • Attachment
  • Bubble
  • Marker
  • Message
  • Message Scroller

Components

Dialog

A window overlaid on the primary window, rendering the content underneath inert.

Playground

—DialogTitle children
—DialogDescription children
—showCloseButton
—DialogFooter showCloseButton
—modal
Usage
Dialog wraps a DialogTrigger and a DialogContent. Inside the content, DialogHeader groups DialogTitle and DialogDescription, and DialogFooter aligns the actions to the end. DialogClose closes from anywhere via asChild; the top-corner close button ships by default.
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@workspace/ui/components/dialog"

<Dialog>
  <DialogTrigger asChild>
    <Button variant="outline">Open dialog</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Are you absolutely sure?</DialogTitle>
      <DialogDescription>
        This action cannot be undone. This will permanently delete your
        account and remove your data from our servers.
      </DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <DialogClose asChild>
        <Button variant="outline">Cancel</Button>
      </DialogClose>
      <Button>Continue</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>
Form
Compose Field parts inside the content for edit-in-place flows. The grid layout of DialogContent spaces header, fields, and footer with a consistent gap-6 — no extra wrappers needed.
<Dialog>
  <DialogTrigger asChild>
    <Button variant="outline">Edit profile</Button>
  </DialogTrigger>
  <DialogContent className="sm:max-w-sm">
    <DialogHeader>
      <DialogTitle>Edit profile</DialogTitle>
      <DialogDescription>
        Make changes to your profile here. Click save when you're done.
      </DialogDescription>
    </DialogHeader>
    <FieldGroup>
      <Field>
        <FieldLabel htmlFor="profile-name">Name</FieldLabel>
        <Input id="profile-name" defaultValue="Pedro Duarte" />
      </Field>
      <Field>
        <FieldLabel htmlFor="profile-username">Username</FieldLabel>
        <Input id="profile-username" defaultValue="@peduarte" />
      </Field>
    </FieldGroup>
    <DialogFooter>
      <DialogClose asChild>
        <Button variant="outline">Cancel</Button>
      </DialogClose>
      <Button type="submit">Save changes</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>
Custom close button
Wrap any Button in DialogClose to close the dialog from custom actions. Here the footer realigns with sm:justify-start for a share-link pattern.
<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="grid gap-2">
      <Label htmlFor="share-link" className="sr-only">
        Link
      </Label>
      <Input
        id="share-link"
        defaultValue="https://westy.la/design-system"
        readOnly
      />
    </div>
    <DialogFooter className="sm:justify-start">
      <DialogClose asChild>
        <Button type="button">Close</Button>
      </DialogClose>
    </DialogFooter>
  </DialogContent>
</Dialog>
Close button visibility
showCloseButton on DialogContent hides the top-corner control; the same prop on DialogFooter renders a ready-made outline Close button instead. Keep at least one visible way out — Escape and overlay clicks still close either way.
<Dialog>
  <DialogTrigger asChild>
    <Button variant="outline">No close button</Button>
  </DialogTrigger>
  <DialogContent showCloseButton={false}>
    <DialogHeader>
      <DialogTitle>No close button</DialogTitle>
      <DialogDescription>
        This dialog hides the top-corner close control and relies on the
        footer instead.
      </DialogDescription>
    </DialogHeader>
    <DialogFooter showCloseButton />
  </DialogContent>
</Dialog>
Scrollable content
Cap a middle region with max-h-[50vh] overflow-y-auto and the header and footer stay pinned while the body scrolls. The negative -mx-6 px-6 lets the scrollbar sit at the dialog edge without clipping the padding.
<Dialog>
  <DialogTrigger asChild>
    <Button variant="outline">Terms of service</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Terms of service</DialogTitle>
      <DialogDescription>
        Read the terms below — the footer stays visible while you scroll.
      </DialogDescription>
    </DialogHeader>
    <div className="-mx-6 max-h-[50vh] overflow-y-auto px-6">
      {Array.from({ length: 10 }).map((_, index) => (
        <p key={index} className="mb-4 leading-normal">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit...
        </p>
      ))}
    </div>
    <DialogFooter>
      <DialogClose asChild>
        <Button variant="outline">Close</Button>
      </DialogClose>
      <Button>Accept</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>
RTL
DialogContent positions itself with logical properties (start-1/2, end-4), so passing dir="rtl" mirrors the close button and footer without extra classes.
<Dialog>
  <DialogTrigger asChild>
    <Button variant="outline">فتح النافذة</Button>
  </DialogTrigger>
  <DialogContent dir="rtl" className="sm:max-w-sm">
    <DialogHeader>
      <DialogTitle>تعديل الملف الشخصي</DialogTitle>
      <DialogDescription>
        قم بإجراء تغييرات على ملفك الشخصي هنا. انقر على حفظ عند الانتهاء.
      </DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <DialogClose asChild>
        <Button variant="outline">إلغاء</Button>
      </DialogClose>
      <Button>حفظ</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>
Accessibility
Built on the Radix Dialog primitive: focus is trapped inside the content, Escape and overlay clicks close it, and focus returns to the trigger on close. DialogTitle labels the dialog via aria-labelledby and is required — if the design hides it, keep it in the tree with sr-only. DialogDescription wires up aria-describedby, and the built-in close button carries a screen-reader-only label.
<DialogContent>
  <DialogHeader>
    <DialogTitle className="sr-only">Search</DialogTitle>
    <DialogDescription className="sr-only">
      Search the documentation
    </DialogDescription>
  </DialogHeader>
  {/* visually title-less content */}
</DialogContent>
API Reference
Ten parts re-export the Radix Dialog primitive with Westy styling: Dialog as the stateful root, DialogTrigger and DialogClose as behavior wrappers (use asChild to keep your own markup), DialogPortal and DialogOverlay handled automatically by DialogContent, plus the DialogHeader / DialogTitle / DialogDescription / DialogFooter layout parts. All accept their underlying element props.
ComponentPropDefault
Dialogopen?: boolean, onOpenChange?: (open: boolean) => void—
Dialogmodal?: booleantrue
DialogContentshowCloseButton?: booleantrue
DialogFootershowCloseButton?: booleanfalse
DialogTrigger / DialogCloseasChild?: booleanfalse
<Dialog data-slot="dialog" />
<DialogTrigger data-slot="dialog-trigger" />
<DialogContent data-slot="dialog-content" data-open data-closed />
<DialogOverlay data-slot="dialog-overlay" />
<DialogHeader data-slot="dialog-header" />
<DialogTitle data-slot="dialog-title" />
<DialogDescription data-slot="dialog-description" />
<DialogFooter data-slot="dialog-footer" />
<DialogClose data-slot="dialog-close" />

Tokens used

9 design tokens consumed by the Dialog component.

  • --popover

    Popover

    bg-popover
  • --popover-foreground

    Popover Foreground

    text-popover-foreground
  • --foreground

    Foreground

    ring-foreground/10*:[a]:hover:text-foreground
  • --muted-foreground

    Muted Foreground

    text-muted-foreground
  • --font-heading

    GT Alpina Condensed

    font-heading
  • --text-sm

    Text Small (14px)

    text-sm
  • --font-weight-medium

    Font weight Medium (500)

    font-medium
  • --radius-xl

    Radius 1.4× (14px)

    rounded-xl
  • --spacing

    Spacing unit (4px)

    p-6gap-6gap-2top-4end-4