Gesso
Overlays

Dialog

A centered modal on the raised surface, with a close button and a fade-and-scale entrance.

Dialog is a centered modal for work that needs the whole of someone's attention, like a rename, a short form, or a detail that does not belong on the page. It sits on the raised surface over a scrim, takes a corner close button by default, and handles focus, Escape, and the page behind it for you. It is built on the same Radix dialog as Sheet, which docks to an edge instead of the center.

Usage

import { Button } from '@/components/ui/button';
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog';

<Dialog>
  <DialogTrigger asChild>
    <Button>Open</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Title</DialogTitle>
      <DialogDescription>Supporting copy.</DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <DialogClose asChild>
        <Button variant="outline">Cancel</Button>
      </DialogClose>
    </DialogFooter>
  </DialogContent>
</Dialog>

What closes it

A dialog closes from the corner button, from any DialogClose, with Escape, and with a click on the scrim. The corner button is on by default, and showCloseButton={false} removes it when the footer already holds the way out. Escape and the scrim still close it, so a dialog that must not be dismissed that way is an Alert dialog.

The corner button is 36px square, and 44px on a touch screen, and it fades in from a resting opacity on hover. DialogHeader keeps its text clear of it, so a long title wraps before the button. The DialogFooter stacks its buttons with the primary action on top on a narrow screen, and lines them up on the right from sm up.

Name it with a title

DialogTitle names the dialog and DialogDescription describes it, and each is wired up only when it is rendered. A dialog with no DialogTitle has no accessible name, and this version of Radix does not warn about it. If a design has no visible title, render one inside a visually hidden wrapper such as sr-only.

Without a trigger

open and onOpenChange control a dialog from your own state, and then a DialogTrigger is optional. Radix returns focus only to a DialogTrigger, so a dialog opened from state loses focus to the page on close unless you send it somewhere. Do that in onCloseAutoFocus on DialogContent.

<DialogContent
  onCloseAutoFocus={(event) => {
    event.preventDefault();
    openerRef.current?.focus();
  }}
>

Size and long content

DialogContent is 448px wide and keeps 16px from each edge of a narrower screen. It is at most the screen's height less 32px, and content taller than that scrolls inside it, the corner button with it. Pass a className such as sm:max-w-lg for a wider dialog, or use Sheet for a long form.

Props

Dialog takes the props of the Radix Dialog it is built on, including open, defaultOpen, onOpenChange, and modal. DialogContent adds one.

PropTypeDefaultWhat it does
showCloseButtonbooleantrueShows the corner close button, labelled "Close" for screen readers.

DialogOverlay and DialogPortal are exported for building a custom content layout. With modal={false} there is no scrim, the page stays usable, and focus is not trapped.

Accessibility

  • DialogContent has role="dialog". aria-labelledby points at DialogTitle and aria-describedby at DialogDescription, each only while it is rendered.
  • Focus moves into the dialog on open, to the first focusable element in the content, and Tab and Shift+Tab loop inside it. The corner button comes last in that order.
  • On close, focus returns to the DialogTrigger. Without one, focus is not returned unless you do it in onCloseAutoFocus.
  • Escape closes it, and so does a click on the scrim, except a right click.
  • While it is open the page behind is hidden from assistive technology with aria-hidden, does not take clicks, and does not scroll.
  • The corner button is a native button named "Close" and shows a 2px focus ring on :focus-visible. It is 36px square, and 44px on a coarse pointer such as a touch screen, at any width.
  • The enter and exit are a fade on the scrim and a fade-and-scale on the content over the base duration, and reduced motion drops both so the dialog appears at once.
  • The kit adds no rules for forced colors to the scrim or the content, and has not been checked in Windows high contrast. The content keeps its border, which the system paints.

When to use something else

  • Alert dialog when the person must answer before doing anything else. It has no close button, and Escape and a click outside do not dismiss it.
  • Confirm for a yes or no question with a verb, a trigger, and a pending state while the action runs.
  • Sheet for a panel that docks to an edge and holds a long form or a detail beside the page.
  • Drawer on phones, for a bottom sheet that rests at heights you choose. A centered dialog is the better fit on a wide screen.

Install

shadcn add @gesso/dialog

On this page