Errori e fix
Shadcn: DialogContent requires a DialogTitle, come risolverlo davvero
Radix avvisa quando un dialog non ha un nome accessibile. La correzione richiede un titolo, anche invisibile: aggiungere solo `aria-label` è il secondo passo.
In sintesi
- L'avviso non è cosmetico: senza titolo il dialog non ha un nome accessibile per uno screen reader.
- La correzione minima è `
` dentro `DialogContent`, con lo stesso testo del contesto. - `aria-label` su `DialogContent` è l'alternativa quando il titolo visivo non esiste per scelta di design.
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 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
- Aggiungi un test che apre ogni dialog e verifica che esista un nodo con
role="dialog"e un nome accessibile. - Nel code review, tratta l'avviso come errore: non è rumore.
- Per i dialog di sola conferma, includi sempre anche una descrizione: il nome dice cosa, la descrizione dice perché.
- Scrivi il titolo con il testo del contesto, non la parola generica del componente.
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.
Domande frequenti
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.