
Développeur Next.js - Freelance
Dialog, Drawer et Modal en Next.js : choisir le bon niveau d'interruption
Des drawers utilisés comme des pages, des dialogs qui scrollent ou qui prennent toute la page, des confirmations inutiles, et des problèmes d’accessibilité.
Le problème, c’est pas le composant. C’est le mauvais niveau d’interruption.
À quel point on bloque l’utilisateur ?
- Zéro : une simple info inline ou un tooltip.
- Maximum : un dialog qui oblige à choisir avant de continuer.
- Entre les deux : panneau non bloquant, drawer, page dédiée.
Choisir le bon niveau évite soit d’interrompre inutilement, soit de noyer une action critique dans le flow utilisateur.
Avant de choisir entre Dialog, Drawer ou page dédiée, il faut maîtriser les bases a11y et le cadre décisionnel. C’est ce que cet article propose : a11y + UX + architecture front + design system.
Les fondamentaux a11y : dialog, modal, alert
Dialog (role="dialog")
En ARIA, un dialog est une zone qui s’affiche par-dessus la page et regroupe du contenu ou des call to actions (boutons, champs, formulaire).
Il peut être modal ou non modal. C’est le même role="dialog", la différence vient de aria-modal et du comportement (focus trap, inert).
Les obligations : un label (aria-labelledby ou aria-label) et une gestion du focus cohérente. Sans ça, les lecteurs d’écran et la navigation clavier deviennent imprévisibles.
En pratique, les librairies de composants comme Base UI, Radix UI, Shadcn ou encore Vaul gèrent le rôle, le label et le focus automatiquement.
Modal dialog
Avec aria-modal="true" :
- Focus trap obligatoire : le focus reste dans le dialog.
- Arrière-plan inerte : le contenu derrière ne reçoit plus le focus ni les clics.
- L’utilisateur doit traiter le dialog avant de revenir au reste de la page.
Modal = comportement, pas apparence : focus piégé dans le dialog et arrière-plan inerte.
Une simple div en position: fixed et z-index élevé ressemble à un dialog, mais la tabulation et les lecteurs d’écran peuvent encore atteindre le contenu derrière : en terme d’accessibilité, ce n’est pas optimal.
Exemples typiques :
- confirmation de suppression (« Supprimer ce projet ? » Oui / Non)
- formulaire court (saisir un nom, une date)
- avertissement bloquant (session expirée, action requise)
- L’arrière-plan est en général assombri pour marquer qu’il est inaccessible
Dialog modal Base UI : interruption explicite, arrière-plan assombri
Non-modal dialog
Sans blocage : l’utilisateur peut interagir avec l’arrière-plan.
Malheureusement souvent sous-utilisé au profit du tout-modal alors qu’il est beaucoup plus UX friendly.
Ce sont les Popovers, Tooltips, Toasts, Dropdowns, etc. qui ne bloquent pas l’utilisateur.
Base UI propose même un composant Drawer non-modal.
Très utile pour :
- des panneaux d’options (filtres, tri)
- infobulles riches (aide contextuelle, raccourcis clavier)
- paramètres rapides
- panneaux de navigation (dans la navbar par exemple)
- informations complémentaires qui ne doivent pas interrompre le flux
Toast non-modal Base UI : pas d'interruption
Alert et Alertdialog : deux rôles ARIA distincts du dialog
En plus du role="dialog", ARIA définit deux rôles dédiés aux messages et confirmations : role="alert" et role="alertdialog".
Ce ne sont pas des variantes du dialog, mais des patterns à part pour des cas précis.
role="alert": message unidirectionnel (succès, erreur, info). Le lecteur d’écran l’annonce tout de suite. L’utilisateur n’a pas d’action obligatoire à faire. Pas bloquant. Par exemple : les toasts.role="alertdialog": confirmation bloquante. L’utilisateur doit choisir une option (Oui / Non, Annuler / Confirmer) avant de continuer. Focus trap et arrière-plan inerte, comme un dialog.
| Rôle ARIA | Bloquant | Action requise |
|---|---|---|
| dialog (modal ou non-modal) | ✅ ou ❌ | ✅ ou ❌ |
| alert (non-modal) | ❌ | ❌ |
| alertdialog (modal) | ✅ | ✅ |
Base UI, Radix UI, Shadcn proposent même des composants AlertDialog qui gèrent automatiquement le rôle, le label et le focus :
AlertDialog Base UI : confirmation bloquante
Dialog vs Drawer : faux débat
Un Drawer n’est pas un pattern ARIA.
C’est une variation spatiale d’un dialog (ouverture latérale, souvent depuis un bord).
Les mêmes enjeux s’appliquent : modal ou non, focus trap, label.
Dialog centré : interruption explicite
- Décision binaire ou action courte.
- Peu de scroll.
- Rupture volontaire avec le contexte : on veut que l’utilisateur se concentre sur cette seule chose.
Dialog Base UI : interruption explicite avec le contenu initial
Drawer latéral : continuité contextuelle
- Dépendance forte au contenu derrière (ex. éditer un élément d’une liste ou d'un tableau).
- Édition dense avec un peu de scroll.
- Garder un repère spatial : l’utilisateur voit encore la liste ou la card d’origine.
Le choix n’est pas esthétique, il est cognitif. Centré = rupture. Latéral = continuité.
Pour les drawers en React, Vaul est une option headless dédiée ; Base UI vient de release son nouveau composant Drawer (en février 2026).
Drawer Base UI : continuité contextuelle avec le contenu initial
Choisir entre Dialog et Drawer, modal ou non-modal
Quatre questions suffisent pour trancher.
Question 1 : L’utilisateur doit-il arrêter ce qu’il fait ?
- Oui : modal (focus trap + inert).
- Non : non-modal ou simple panel.
Question 2 : Le contenu dépend-il visuellement de l’arrière-plan ?
- Oui (ex. détail d’une ligne) : drawer.
- Non (ex. confirmation générique) : dialog centré.
Question 3 : Le contenu mérite-t-il une URL ?
- Oui (partage, deep link, historique) : page dédiée.
- Non : overlay (dialog ou drawer).
Question 4 : Quelle est la densité ?
- Court (quelques champs, une décision) : dialog.
- Long (formulaire complexe, workflow) : drawer ou page, pas un dialog qui scroll indéfiniment.
Résumé des user flows typiques
Selon la situation
| Faible densité | Forte densité | |
|---|---|---|
| Faible interruption | Tooltip / Popover | Drawer non-modal |
| Forte interruption | Dialog court centré | Drawer modal ou Page dédiée |
Exemples concrets
| Situation | Pattern recommandé |
|---|---|
| Message système | alert |
| Confirmation critique | alertdialog |
| Formulaire court | dialog |
| Édition dense contextuelle | drawer |
| Workflow majeur | page dédiée |
Les erreurs fréquentes
Modal sans focus trap
Un drawer qui bloque l’arrière-plan mais ne piège pas le focus : la tabulation « sort » du drawer, la navigation clavier et les lecteurs d’écran deviennent incohérents.
Avec les librairies dédiées comme Base UI, Radix, Headless UI, etc. : le focus trap est géré nativement.
Dialog scrollable sur toute la hauteur
Un dialog qui fait 90 % de l’écran et scroll comme une page : charge cognitive élevée et perte du repère « c’est une interruption courte ».
Dans ce cas, mieux vaut passer à un drawer voire même une page dédiée.
Confirmation Dialog superflue
« Êtes-vous sûr de vouloir enregistrer ? » pour une action non destructive : ça agace et habitue à fermer sans lire.
Réserver les dialogs aux vraies confirmations comme une suppression ou un engagement légal.
Workflow complexe
Multi-étapes, formulaires longs dans un dialog : pas d’URL, pas d’historique, pas de deep link.
Navigation clavier et historique navigateur ne reflètent pas l’état. Dans ce cas, préférer une page dédiée.
Multiplication des overlays empilés
Dialog sur Dialog ou Drawer sur Drawer : focus et annonces lecteur d’écran se mélangent.
Il vaut mieux limiter l’empilement (un overlay à la fois) ou prévoir une librairie qui gère correctement le focus et l'accessibilité comme Vaul ou Base UI pour les drawers.
Pour les Dialogs, il vaut mieux éviter l'empilement.
Checklist a11y minimale pour un élément modal
- Focus initial : envoyer le focus sur le premier élément focusable ou le conteneur.
- Focus trap : garder le focus à l’intérieur tant que l'élément modal est ouvert (Tab / Shift+Tab en boucle).
- Restore focus : à la fermeture, remettre le focus sur l’élément qui a ouvert l'élément modal.
- aria-labelledby (ou aria-label) : associer un titre au dialog.
- Touche Escape : fermer l'élément modal et restaurer le focus.
- Arrière-plan inerte : empêcher focus et clics sur le contenu derrière (natif avec
<dialog>+showModal(), ouinert).
Avec <dialog> et showModal(), le navigateur gère nativement une partie (backdrop, Escape, inert).
Avec une solution custom, tout doit être implémenté ou délégué à une librairie (headless ou déjà stylisée).
Vous pouvez retrouver une multitude de librairies headless dans cet article. Il est également important de bien choisir ses librairies (notamment en se basant sur mon autre article sur le sujet).
Pourquoi les librairies n’utilisent presque jamais <dialog> ?
Sur le papier, <dialog> semble parfait : backdrop natif, showModal(), gestion d’Escape, inert automatique.
En pratique, les librairies l’évitent souvent.
Pourquoi ?
- Contrôle limité du rendu : le pseudo-élément ::backdrop est stylisable, mais reste moins flexible qu’une overlay custom.
- Compatibilité : comportement variable selon les navigateurs.
- Gestion SSR / hydration :
showModal()est impératif et peut créer des incohérences entre serveur et client. - Empilement complexes : les dialogs imbriqués et la gestion du focus deviennent compliqués à maintenir.
- Design system : on a souvent besoin d'un contrôle total sur l’overlay, l’animation, le scroll lock, etc.
Résultat : les librairies préfèrent recréer le pattern (focus trap, inert, aria-modal) pour garder une maîtrise complète du comportement.
<dialog> n’est pas “mauvais”.
Il est simplement moins flexible dans des architectures complexes comme React et Next.js.
Conclusion : le bon niveau d’interruption
Une interface mature maîtrise ses interruptions.
Plus on bloque, plus il faut le justifier. Les éléments modal (Dialog, Drawer) sont des outils puissants, mais coûteux cognitivement.
Il faut les réserver aux cas où l’interruption apporte une réelle valeur.
Résumé des décisions importantes : éviter les dialogs scroll géant et les confirmations superflues.
Pour aller plus loin, The A11Y Collective — Modal vs Dialog et les docs Radix Dialog, Headless UI Dialog, Base UI Dialog et Vaul sont des références solides.