HextaUI

Alert dialog

Une boîte de dialogue de confirmation pour les actions destructrices ou importantes, qui attend le travail asynchrone et devient une bottom sheet sur mobile.

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

export function AlertDialogDemo() {
  return (
    <AlertDialog>
      <AlertDialogTrigger render={<Button variant="outline" />}>
        Show dialog
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Publish changes?</AlertDialogTitle>
          <AlertDialogDescription>
            Your changes will be visible to everyone with the link.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction>Publish</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/alert-dialog.json

Ajoute le composant, les tokens de thème HextaUI et les composants HextaUI dont il dépend.

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
<AlertDialog>
  <AlertDialogTrigger render={<Button variant="outline" />}>
    Delete
  </AlertDialogTrigger>
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Delete this project?</AlertDialogTitle>
      <AlertDialogDescription>This can’t be undone.</AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogAction>Delete</AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>
AlertDialog
├── AlertDialogTrigger
└── AlertDialogContent
    ├── AlertDialogHeader
    │   ├── AlertDialogMedia
    │   ├── AlertDialogTitle
    │   └── AlertDialogDescription
    └── AlertDialogFooter
        ├── AlertDialogCancel
        └── AlertDialogAction

Action asynchrone

Retournez une promesse depuis le onClick de <AlertDialogAction />. Le bouton affiche un spinner et la boîte de dialogue ne peut pas être fermée avant que la promesse soit réglée. Elle se ferme à la résolution et reste ouverte en cas de rejet, pour que l’on puisse réessayer.

"use client"

import * as React from "react"
import { IconTrash } from "@tabler/icons-react"

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogMedia,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

function wait(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms))
}

export function AlertDialogAsyncAction() {
  const [shouldFail, setShouldFail] = React.useState(false)
  const [attempts, setAttempts] = React.useState(0)

  return (
    <div className="flex flex-wrap items-center gap-2">
      <AlertDialog onOpenChange={(open) => open && setAttempts(0)}>
        <AlertDialogTrigger render={<Button variant="destructive" />}>
          Delete project
        </AlertDialogTrigger>
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogMedia variant="destructive">
              <IconTrash />
            </AlertDialogMedia>
            <AlertDialogTitle>Delete “Acme website”?</AlertDialogTitle>
            <AlertDialogDescription>
              {attempts > 0
                ? `Deleting failed ${attempts} time${attempts > 1 ? "s" : ""}. Try again.`
                : "All deployments, domains and environment variables will be removed. This can’t be undone."}
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>Cancel</AlertDialogCancel>
            <AlertDialogAction
              variant="destructive"
              onClick={async () => {
                await wait(1500)
                if (shouldFail) {
                  setAttempts((count) => count + 1)
                  throw new Error("Request failed")
                }
              }}
            >
              Delete project
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
      <Button
        variant="ghost"
        size="sm"
        aria-pressed={shouldFail}
        onClick={() => setShouldFail(!shouldFail)}
      >
        Request will {shouldFail ? "fail" : "succeed"}
      </Button>
    </div>
  )
}

Média

<AlertDialogMedia /> place une icône à côté du titre sur ordinateur et au-dessus sur téléphone. Utilisez variant="destructive" pour les actions destructrices.

import { IconTrash } from "@tabler/icons-react"

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogMedia,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

export function AlertDialogWithMedia() {
  return (
    <AlertDialog>
      <AlertDialogTrigger render={<Button variant="outline" />}>
        Delete file
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogMedia variant="destructive">
            <IconTrash />
          </AlertDialogMedia>
          <AlertDialogTitle>Delete “invoice-2026.pdf”?</AlertDialogTitle>
          <AlertDialogDescription>
            The file moves to the trash and is removed after 30 days.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction variant="destructive">Delete</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}

Petit

size="sm" centre le contenu et place les boutons côte à côte, pour les questions courtes.

import { IconLogout } from "@tabler/icons-react"

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogMedia,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

export function AlertDialogSmall() {
  return (
    <AlertDialog>
      <AlertDialogTrigger render={<Button variant="outline" />}>
        Sign out
      </AlertDialogTrigger>
      <AlertDialogContent size="sm">
        <AlertDialogHeader>
          <AlertDialogMedia>
            <IconLogout />
          </AlertDialogMedia>
          <AlertDialogTitle>Sign out?</AlertDialogTitle>
          <AlertDialogDescription>
            You’ll need to sign in again on this device.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction>Sign out</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}

Déclencheurs détachés

Créez un handle avec createAlertDialogHandle() pour partager une même boîte de dialogue entre plusieurs déclencheurs. Chaque déclencheur transmet son propre payload, que la boîte lit via une fonction de rendu.

"use client"

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
  createAlertDialogHandle,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

const removeMember = createAlertDialogHandle<{ name: string }>()

export function AlertDialogDetachedTriggers() {
  return (
    <div className="flex flex-wrap gap-2">
      {["Olivia", "Liam"].map((name) => (
        <AlertDialogTrigger
          key={name}
          handle={removeMember}
          payload={{ name }}
          render={<Button variant="outline" />}
        >
          Remove {name}
        </AlertDialogTrigger>
      ))}
      <AlertDialog handle={removeMember}>
        {({ payload }) => (
          <AlertDialogContent size="sm">
            <AlertDialogHeader>
              <AlertDialogTitle>Remove {payload?.name}?</AlertDialogTitle>
              <AlertDialogDescription>
                They’ll lose access to this workspace.
              </AlertDialogDescription>
            </AlertDialogHeader>
            <AlertDialogFooter>
              <AlertDialogCancel>Cancel</AlertDialogCancel>
              <AlertDialogAction variant="destructive">
                Remove
              </AlertDialogAction>
            </AlertDialogFooter>
          </AlertDialogContent>
        )}
      </AlertDialog>
    </div>
  )
}

Contrôlé

Passez open et onOpenChange pour l’ouvrir depuis le code, sans déclencheur. Le focus arrive toujours sur Cancel.

"use client"

import * as React from "react"

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

export function AlertDialogControlled() {
  const [open, setOpen] = React.useState(false)

  return (
    <>
      <Button
        variant="outline"
        onClick={() => window.setTimeout(() => setOpen(true), 600)}
      >
        Open in 600ms
      </Button>
      <AlertDialog open={open} onOpenChange={setOpen}>
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogTitle>Session expiring</AlertDialogTitle>
            <AlertDialogDescription>
              You’ve been inactive for a while. Stay signed in?
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>Sign out</AlertDialogCancel>
            <AlertDialogAction>Stay signed in</AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </>
  )
}

Imbriqué

Une boîte de dialogue ouverte depuis une autre s’empile par-dessus. La parente se réduit tant que l’enfant est ouvert et revient au premier plan à sa fermeture.

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

export function AlertDialogNested() {
  return (
    <AlertDialog>
      <AlertDialogTrigger render={<Button variant="outline" />}>
        Reset settings
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Reset all settings?</AlertDialogTitle>
          <AlertDialogDescription>
            Theme, notifications and shortcuts go back to their defaults.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialog>
            <AlertDialogTrigger render={<Button variant="destructive" />}>
              Reset
            </AlertDialogTrigger>
            <AlertDialogContent size="sm">
              <AlertDialogHeader>
                <AlertDialogTitle>Are you really sure?</AlertDialogTitle>
                <AlertDialogDescription>
                  The parent dialog steps back while this one is open.
                </AlertDialogDescription>
              </AlertDialogHeader>
              <AlertDialogFooter>
                <AlertDialogCancel>Go back</AlertDialogCancel>
                <AlertDialogAction variant="destructive">
                  Reset
                </AlertDialogAction>
              </AlertDialogFooter>
            </AlertDialogContent>
          </AlertDialog>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}

Contenu long

Un contenu plus haut que l’écran défile à l’intérieur de la boîte de dialogue pendant que la page reste verrouillée.

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

const sections = Array.from({ length: 12 }, (_, index) => index + 1)

export function AlertDialogLongContent() {
  return (
    <AlertDialog>
      <AlertDialogTrigger render={<Button variant="outline" />}>
        Review terms
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Accept the updated terms?</AlertDialogTitle>
          <AlertDialogDescription render={<div />}>
            {sections.map((section) => (
              <p key={section}>
                Section {section}. We may update these terms from time to time.
                Continued use of the service after changes take effect means you
                accept the new terms. Read the <a href="#">full policy</a> for
                details.
              </p>
            ))}
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Decline</AlertDialogCancel>
          <AlertDialogAction>Accept</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}

De droite à gauche

Passez aussi dir au contenu, car il s’affiche dans un portail en dehors de votre conteneur RTL. Les flèches du pied de page suivent le sens de lecture.

import { IconTrash } from "@tabler/icons-react"

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogMedia,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

export function AlertDialogRtl() {
  return (
    <div dir="rtl">
      <AlertDialog>
        <AlertDialogTrigger render={<Button variant="outline" />}>
          حذف الملف
        </AlertDialogTrigger>
        <AlertDialogContent dir="rtl">
          <AlertDialogHeader>
            <AlertDialogMedia variant="destructive">
              <IconTrash />
            </AlertDialogMedia>
            <AlertDialogTitle>حذف هذا الملف؟</AlertDialogTitle>
            <AlertDialogDescription>
              لا يمكن التراجع عن هذا الإجراء.
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>إلغاء</AlertDialogCancel>
            <AlertDialogAction variant="destructive">حذف</AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </div>
  )
}
ToucheAction
EnterSpaceSur le déclencheur, ouvre la boîte de dialogue et place le focus sur Cancel.
TabShift+TabDéplace le focus entre les contrôles. Le focus reste à l’intérieur.
←→↑↓Passe d’un bouton du pied de page à l’autre en bouclant. Gauche et droite suivent le sens de lecture.
EscFerme la boîte de dialogue et rend le focus au déclencheur. Ignoré pendant l’exécution d’une action asynchrone.
  • Le contenu a role="alertdialog", est étiqueté par son titre et décrit par sa description.
  • Le focus démarre sur <AlertDialogCancel />, le choix le moins destructeur. Passez initialFocus pour le modifier.
  • Un clic sur l’arrière-plan ne la ferme pas : une décision n’est donc jamais écartée par accident. Définissez disablePointerDismissal={false} pour l’autoriser.
  • Sur téléphone, elle devient une bottom sheet qu’on peut balayer vers le bas pour annuler. Pendant qu’une action est en cours, le balayage, Esc et Cancel sont bloqués et les boutons restent focusables.
  • Avec la réduction des animations, elle apparaît en fondu au lieu de changer d’échelle.

Construit sur le drawer de Base UI, via <Sheet />. Chaque partie accepte les props de la primitive ou de l’élément qu’elle enveloppe.

PropTypePar défaut
open
boolean–
defaultOpen
booleanfalse
onOpenChangeNon appelé pour les fermetures bloquées par une action en cours.
(open: boolean, details) => void–
onOpenChangeCompleteAppelé après l’animation d’ouverture ou de fermeture.
(open: boolean) => void–
disablePointerDismissalGarde la boîte de dialogue ouverte lors d’un clic sur l’arrière-plan.
booleantrue
handleRelie des déclencheurs détachés.
AlertDialogHandle<Payload>–
actionsRefFerme ou démonte la boîte de dialogue de façon impérative.
RefObject<{ close, unmount }>–
children
ReactNode | ({ payload }) => ReactNode–
PropTypePar défaut
handle
AlertDialogHandle<Payload>–
payloadTransmis à la fonction de rendu de la boîte de dialogue.
Payload–
render
ReactElement | (props, state) => ReactElement<button>
AttributDescription
data-slot="alert-dialog-trigger"Ciblez les déclencheurs en CSS.
data-popup-openPrésent tant que sa boîte de dialogue est ouverte.
PropTypePar défaut
size
"default" | "sm""default"
initialFocus
boolean | RefObject | (openType) => HTMLElement | booleanCancel button
finalFocus
boolean | RefObject | (closeType) => HTMLElement | booleanThe trigger
dirÀ définir lorsque la boîte de dialogue doit être de droite à gauche.
"ltr" | "rtl"–
render
ReactElement | (props, state) => ReactElement<div>
AttributDescription
data-slot="alert-dialog-content"Ciblez la boîte de dialogue en CSS.
data-sizeLa taille actuelle.
data-openPrésent tant que l’élément est ouvert.
data-starting-stylePrésent pendant l’animation d’ouverture de la boîte de dialogue.
data-ending-stylePrésent pendant l’animation de fermeture de la boîte de dialogue.
data-nested-drawer-openPrésent tant qu’une boîte de dialogue imbriquée est ouverte par-dessus.
data-swipingPrésent pendant qu’on la balaie sur téléphone.
--nested-drawersNombre de boîtes de dialogue ouvertes par-dessus celle-ci.

Un <div> qui empile le média, le titre et la description.

AttributDescription
data-slot="alert-dialog-header"Ciblez l’en-tête en CSS.
PropTypePar défaut
variant
"default" | "destructive""default"
AttributDescription
data-slot="alert-dialog-media"Ciblez le média en CSS.
PropTypePar défaut
render
ReactElement | (props, state) => ReactElement<h2>
AttributDescription
data-slot="alert-dialog-title"Ciblez le titre en CSS.
PropTypePar défaut
renderUtilisez render={<div />} lorsqu’il contient plusieurs paragraphes.
ReactElement | (props, state) => ReactElement<p>
AttributDescription
data-slot="alert-dialog-description"Ciblez la description en CSS.

Un <div> pour les boutons. Les boutons s’empilent sur toute la largeur sur téléphone, et les flèches permettent de passer de l’un à l’autre.

AttributDescription
data-slot="alert-dialog-footer"Ciblez le pied de page en CSS.

Un <Button /> qui ferme la boîte de dialogue au clic. Il accepte toutes les props de Button.

PropTypePar défaut
onClickRetournez une promesse pour afficher un spinner et garder la boîte de dialogue ouverte jusqu’à son règlement. Appelez event.preventDefault() pour la garder ouverte.
(event) => void | PromiseLike<unknown>–
variant
ButtonVariant"default"
AttributDescription
data-slot="alert-dialog-action"Ciblez les actions en CSS.
PropTypePar défaut
variant
ButtonVariant"outline"
size
ButtonSize–
disabledÉgalement désactivé pendant qu’une action est en cours.
boolean–
AttributDescription
data-slot="alert-dialog-cancel"Ciblez le bouton d’annulation en CSS. Il reçoit le focus initial.
data-disabledPrésent lorsque l’élément est désactivé.

<AlertDialogContent /> affiche déjà les deux. Utilisez-les uniquement pour composer une fenêtre personnalisée.

PropTypePar défaut
keepMountedSur le portail, garde la boîte de dialogue dans le DOM lorsqu’elle est fermée.
booleanfalse

Retourne un handle qui relie des éléments <AlertDialogTrigger /> placés n’importe où dans la page à un seul <AlertDialog />. Typez le payload avec un générique : createAlertDialogHandle<{ name: string }>().

Utilisé dans les blocks

Des blocks qui s’appuient sur Alert dialog.