Dialog
A window overlaid on either the primary window or another dialog window.
Edit profile
Make changes to your profile here. Click save when you're done.
Dialog est une modale centrée qui interrompt le flow utilisateur pour une action précise (édition d'un profil, confirmation d'une opération, formulaire compact). Contrairement à un Sheet ou Drawer qui glissent depuis le bord, la Dialog apparaît au centre de l'écran avec un backdrop bloquant.
Architecture PRISM : le trigger est un simple bouton avec data-ui="toggle-pop" data-target="#dlg-id". Le content est un <div data-overlay hidden> avec l'id correspondant. L'island ui.js intercepte le clic sur le trigger, retire hidden, applique overflow:hidden au body. Escape / clic backdrop / data-ui="close" referme.
Le lien trigger↔content passe par un id partagé, pas par un context React. C'est plus verbeux mais permet plusieurs Dialog sur la même page sans conflit d'état.
Composition idiomatique :
- DialogHeader — titre + description au-dessus du corps.
- DialogTitle —
<h2>semantique, rôle heading pour les AT. - DialogDescription — paragraphe muted, lu par les lecteurs d'écran après le titre.
- DialogFooter — barre d'actions en bas (typiquement Cancel + Save alignés à droite).
- DialogClose — wrapper qui ferme le dialog au clic sur son enfant (utilise
data-ui="close").
Pour une confirmation destructive (Delete account, Discard changes), préférer AlertDialog qui force une action explicite (pas de fermeture par clic backdrop).
Installation
$ bext ui add dialog
Copy and paste the following into src/components/ui/dialog.tsx.
This component is interactive — make sure the shared island is loaded (see Islands). It reacts to data-ui="dialog" hooks.
Usage
Anatomy
<Dialog>
├── <DialogTrigger for="dlg-id"> // data-ui="toggle-pop"
│ └── <Button>Edit Profile</Button>
└── <DialogContent id="dlg-id"> // data-overlay hidden
├── <DialogHeader>
│ ├── <DialogTitle>Edit profile</DialogTitle>
│ └── <DialogDescription>...</DialogDescription>
├── <div>...form fields...</div> // corps libre
└── <DialogFooter>
└── <DialogClose>
└── <Button>Save</Button>API Reference
Dialog
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Wrapper vide (inline-block). Contient trigger + content. |
DialogTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
| for | string | — | Doit matcher l'id du DialogContent à ouvrir. Génère data-target="#for". |
| children | ReactNode | — | Bouton ou lien qui déclenche l'ouverture. |
DialogContent
| Prop | Type | Default | Description |
|---|---|---|---|
| id | string | — | Doit matcher DialogTrigger.for. Sert au data-target du toggle-pop. |
| className | string | — | Classes CSS. Défaut : centered fixed, w-full max-w-lg, bg-background rounded-lg border shadow-lg. |
DialogHeader
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | flex flex-col gap-1.5 text-center sm:text-left, au-dessus du corps. |
DialogTitle
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | stylé text-lg font-semibold leading-none tracking-tight. |
DialogDescription
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | stylé text-sm text-muted-foreground. |
DialogFooter
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Barre d'actions bas. flex-col-reverse sur mobile, flex-row justify-end sur sm+. |
DialogClose
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Wrap un bouton/lien avec data-ui="close" pour fermer le dialog. |
Data Attributes
Attributs qu'on peut cibler en CSS custom pour override le style par état.
| Attribute | On | Values | Description |
|---|---|---|---|
| data-ui="toggle-pop" | DialogTrigger | data-target="#dlg-id" | Cliquer ouvre l'overlay ciblé. |
| data-overlay | DialogContent | hidden par défaut | Marqueur pour l'island : à retirer/ajouter le hidden au toggle. body.overflow=hidden quand pas de data-light. |
| data-ui="close" | DialogClose | — | Cliquer ferme le premier ancêtre [data-overlay]. |
Keyboard
| Key | Action |
|---|---|
| Tab / Shift+Tab | Focus trap dans le dialog — cycle uniquement les éléments focusables intérieurs. |
| Enter | Activate le bouton focus (par défaut le premier submit / bouton primary du footer). |
| Escape | Ferme le dialog. Le focus revient au trigger d'origine. |
Accessibility
Le DialogContent pose role="dialog" aria-modal="true". Il faut aussi lier aria-labelledby vers l'id du DialogTitle et aria-describedby vers l'id du DialogDescription pour que le screen reader annonce correctement l'ouverture.
Focus trap : quand le dialog ouvre, le focus est déplacé sur le premier élément focusable intérieur. Tab reste piégé à l'intérieur (cycle back sur le premier). Escape ou fermeture ramène le focus au trigger d'origine.
Backdrop : par défaut, cliquer en dehors ferme le dialog. Pour un formulaire non-triviale ou une action critique, préférer AlertDialog qui force une décision explicite.
Nested dialogs : possible mais déconseillé — préférer un flow linéaire (Dialog → refermer → Dialog suivant).
Examples
Confirmation simple (single button)
Delete this item ?
This action cannot be undone. The item will be permanently removed.
Header sans description
Invite team members
Edit this component live in the bext playground. The PRISM + signals version is the bext-native idiom — fine-grained reactivity, no virtual DOM.
On This Page