React

An accessible shadcn dialog

Every DialogContent needs a DialogTitle, visible or hidden, plus a description or an explicit statement that there is none. Radix handles the rest.

Key points

  • A DialogContent without a DialogTitle logs a warning and leaves the dialog with no accessible name.
  • The title may be invisible: hide it with VisuallyHidden rather than omitting it.
  • Focus trap, Esc and focus restore come from Radix, under the MIT licence.

Short answer: every DialogContent must hold a DialogTitle, even when the title is invisible. Radix links that title to the dialog through aria-labelledby, and without it the dialog has no name for a screen reader.

The minimum that holds

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

export function DeleteDialog({ name }: { name: string }) {
  return (
    
      
        Elimina
      
      
        
          Eliminare {name}?
          Non si può annullare.
        
        
          
            Annulla
          
          Conferma
        
      
    
  );
}

The button that opens the dialog keeps its text, so it already has an accessible name: an aria-label here would be noise. The asChild on DialogTrigger avoids a button inside another button, which is the fastest way to break keyboard navigation.

What you get for free

  • Focus trap: tab stays inside the dialog while it is open, and focus returns to the trigger on close.
  • Esc and outside click: both default exits work, and onEscapeKeyDown lets you block them when needed.
  • Attributes: role="dialog", aria-modal and the references to title and description all come from Radix.
  • Background scroll: the body stops scrolling, without the jump you get when you block it with overflow: hidden.

The title nobody sees

An action-only dialog may have no visible title, yet the title is still required: mount DialogTitle inside a container that hides it visually, while keeping it in the DOM. The text stays readable by a screen reader and the aria-labelledby reference still points at an existing node.

The recurring traps

A DropdownMenu inside a DialogContent moves its menu into an external portal, and a click outside the dialog closes everything: the sequence is described in the dialog closes from a dropdown. A dialog inside another dialog needs care with focus, because the last one opened has to take control.

The command

npx shadcn@latest add dialog

Live records in the catalogue

Published dialogs show the variants: dialog-01 for confirmations, the dialog blocks category for the other cases, and the shadcn library for the base component. Every record names its licence and source.

The limitation

A dialog with no descriptive text asks you to declare that the reference is absent, otherwise the console warns on every open. The component cannot guess whether a description is useful: that call belongs to the content, and no automated test makes it for you. The checklist is in accessible React components.

Where to continue

The missing-title error is analysed in DialogContent requires DialogTitle; the role of ARIA is explained in what ARIA is; the opposite abuse is in aria-label on buttons. For a full check read how to run an accessibility audit.

Frequently asked questions

Can I skip DialogTitle when the title is invisible?

No. The title may be visually hidden, but it has to exist in the DOM, because Radix uses it as the accessible name of the dialog through aria-labelledby.

Why does the DialogContent warning appear?

Because a DialogTitle or a DialogDescription is missing, and Radix reports the missing reference. Add both components, or state explicitly that there is no description.

Does the dialog move focus on its own?

Yes. Focus enters the content on open, stays trapped while the dialog is open and returns to the trigger on close. That belongs to Radix, not to code you write.

Does the dialog need aria-label?

No, not when a DialogTitle exists: the name comes from there. An extra aria-label adds nothing, and with a title present the two texts can drift apart.

Is a dialog right for a destructive confirmation?

Yes, as long as the destructive button says what it does in words and not only in colour, and the title names what is being deleted. Initial focus is best on the cancel action, not on the destructive one.