# Shadcn: DialogContent requires a DialogTitle, and how to fix it properly

Radix warns when a dialog has no accessible name. The fix needs a title, even an invisible one, and adding only `aria-label` is the second step.

> Page: https://ui.eneawork.it/blog/shadcn-dialog-content-richiede-dialogtitle
> Markdown: https://ui.eneawork.it/blog/shadcn-dialog-content-richiede-dialogtitle.md

*2026-11-01*

Risposta breve: Radix avvisa che il dialog non ha un nome accessibile. Aggiungi <DialogTitle> dentro <DialogContent>, visibile o con className="sr-only", e l'avviso sparisce insieme al problema.

Il messaggio non ha una domanda dedicata con punteggio alto su Stack Overflow, quindi la misura utile è un'altra: il tag accessibility conta 8.264 domande sulla Stack Exchange API al 31 ottobre 2026, e il nome accessibile è una delle cause più frequenti. Il catalogo EneUI misura 111.985 componenti, 1.556 blocchi, 12.808 icone da 1.472 librerie, valori misurati e letti da /api/stats il 1 novembre 2026. La verifica si fa nella console del browser e in un test: nessuna estensione.

## Il messaggio esatto

`DialogContent` requires a `DialogTitle` for the component to be accessible for screen reader users. If you are not rendering a visible title, you can use the `DialogTitle` component with the `sr-only` class.

## Perché succede

Un dialog è una finestra modale: uno screen reader deve annunciare cosa si è aperto. Radix collega il titolo al contenitore con aria-labelledby; se non trova il titolo, non ha un nome da annunciare e lo segnala in console.

Aggiungere solo uno stile non basta: il nome accessibile deve esistere nel DOM. Un <h2> fuori dal contenuto del dialog non viene collegato automaticamente.

## Come Radix costruisce il nome

Quando DialogContent monta, Radix genera un id con useId, lo mette sul nodo del titolo e lo collega al contenitore tramite aria-labelledby. Lo stesso meccanismo vale per DialogDescription e aria-describedby. Il titolo non deve essere visibile: deve solo esistere nell'albero React mentre il dialog è aperto, perché il contesto che Radix legge è popolato al montaggio del componente.

Il contenuto viene renderizzato in un portal, fuori dal nodo che contiene il trigger. Per questo un titolo scritto accanto al trigger, o in un componente che vive più in alto, non partecipa: Radix guarda solo il contesto del dialog, non il documento. La verifica che fa è binaria, titolo presente o assente, e il messaggio appare quando il contesto è vuoto.

## La correzione

import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; export function ConfirmDelete() { return ( Elimina Conferma eliminazione L'operazione non può essere annullata. ); }

Quando il titolo visivo esiste già nel design, usa quello e togli sr-only: un titolo visibile è meglio di uno invisibile, perché aiuta anche chi non usa uno screen reader.

Se il design non prevede un titolo e non vuoi un nodo nascosto, passa un nome direttamente al contenuto: <DialogContent aria-label="Conferma eliminazione">. Radix accetta aria-label e aria-labelledby come alternativa.

## Il passo diagnostico

// browser console, with the dialog open const d = document.querySelector('[role="dialog"]'); const id = d && d.getAttribute("aria-labelledby"); console.log({ id, resolves: Boolean(id && document.getElementById(id)) });

Se id è null, il dialog non ha un nome collegato e la correzione non è ancora passata. Se id esiste ma resolves è false, il riferimento punta a un nodo che non c'è più: succede quando due dialog condividono lo stesso titolo tramite un id scritto a mano. Se entrambi i valori sono buoni e l'avviso resta, il problema è un secondo dialog montato fuori schermo, non quello che stai guardando.

## Quando la correzione non funziona

Se l'avviso resta, controlla di aver messo DialogTitle dentro DialogContent e non dentro DialogTrigger: il trigger viene renderizzato fuori dal portale e non partecipa al nome. Un secondo caso è un wrapper che sostituisce il contenuto del dialog con una versione custom: se il wrapper non espone DialogTitle, Radix non lo vede.

## Quando la correzione comune peggiora le cose

La scorciatoia è copiare <DialogTitle className="sr-only">Dialog</DialogTitle> in ogni componente. Funziona per l'avviso, non per chi ascolta: tutti i dialog della pagina si annunciano con la stessa parola, e chi naviga con uno screen reader non sa più quale finestra ha aperto. Peggio, chi aggiunge anche aria-label crede di aver corretto il testo: quando aria-labelledby è presente, il nome calcolato viene dal titolo e aria-label viene ignorata. Il testo resta quello generico scritto nel titolo.

## Un limite con un nome

Limite dichiarato di questa protezione: l'avviso esiste solo in sviluppo. Radix lo stampa con console.warn nel build di sviluppo e lo rimuove da quello di produzione, quindi un dialog senza nome arriva in produzione senza rumore. Nessun errore, nessun crash: solo un utente con screen reader davanti a un contenitore muto. La sola difesa che resta è un test che apre il dialog e interroga il ruolo, non la console.

## Come prevenirlo

## Dove continuare

Guarda un dialog completo nel catalogo: blocco dialog con preview, oppure sfoglia la categoria dialog e tutti i componenti e il glossario per i termini di accessibilità. Le guide sono checklist di accessibilità React e dark mode senza duplicare il design system; il confronto tra primitive è Radix, Base UI e React Aria.

## FAQ

### Posso disattivare l'avviso?

Non conviene. L'avviso segnala un problema di accessibilità reale: un dialog senza nome. La correzione costa una riga.

### Il titolo deve essere visibile?

No. Se il design non prevede un titolo, usa `className="sr-only"`: resta nel DOM e viene letto dagli screen reader.

### Basta `aria-label` su DialogContent?

Sì, se il valore descrive il contenuto. Radix accetta `aria-label` e `aria-labelledby` come nome accessibile alternativo.

### Serve anche DialogDescription?

Radix avvisa separatamente quando manca la descrizione. Se il dialog ha testo introduttivo, usala; altrimenti `aria-describedby={undefined}` è la scelta esplicita.

### Vale per Sheet e Drawer?

Sì: sono la stessa primitiva Radix con un altro stile. Il nome accessibile serve in tutti i casi.
