HextaUI

Alert dialog

Um diálogo de confirmação para ações destrutivas ou importantes que aguarda trabalho assíncrono e vira um bottom sheet no celular.

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

Adiciona o componente, os tokens de tema do HextaUI e quaisquer componentes do HextaUI dos quais ele depende.

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

Ação assíncrona

Retorne uma promise do onClick de <AlertDialogAction />. O botão exibe um spinner e o diálogo não pode ser fechado até a promise ser resolvida ou rejeitada. Ele fecha quando a promise é resolvida e permanece aberto quando é rejeitada, para que as pessoas possam tentar de novo.

"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 /> coloca um ícone ao lado do título no desktop e acima dele em celulares. Use variant="destructive" para ações destrutivas.

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>
  )
}

Pequeno

size="sm" centraliza o conteúdo e dispõe os botões lado a lado, para perguntas curtas.

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>
  )
}

Gatilhos desanexados

Crie um handle com createAlertDialogHandle() para compartilhar um diálogo entre vários gatilhos. Cada gatilho passa seu próprio payload, que o diálogo lê por meio de uma render function.

"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>
  )
}

Controlado

Passe open e onOpenChange para abri-lo por código, sem gatilho. O foco ainda vai para 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>
    </>
  )
}

Aninhado

Um diálogo aberto de dentro de outro fica empilhado por cima. O pai diminui de escala enquanto o filho está aberto e volta ao normal quando ele fecha.

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>
  )
}

Conteúdo longo

Conteúdo mais alto que a tela rola dentro do diálogo enquanto a página permanece bloqueada.

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>
  )
}

Da direita para a esquerda

Passe dir também ao conteúdo, pois ele é renderizado em um portal fora do seu contêiner RTL. As teclas de seta no rodapé seguem a direção de leitura.

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>
  )
}
TeclaAção
EnterSpaceNo gatilho, abre o diálogo e move o foco para Cancel.
TabShift+TabMove o foco entre os controles. O foco permanece dentro.
←→↑↓Alterna entre os botões do rodapé e volta ao início ao chegar no fim. Esquerda e direita seguem a direção de leitura.
EscFecha o diálogo e devolve o foco ao gatilho. Ignorado enquanto uma ação assíncrona está em andamento.
  • O conteúdo tem role="alertdialog", é rotulado pelo título e descrito pela descrição.
  • O foco começa em <AlertDialogCancel />, a escolha menos destrutiva. Passe initialFocus para alterá-lo.
  • Clicar no backdrop não fecha o diálogo, então uma decisão nunca é descartada por acidente. Defina disablePointerDismissal={false} para permitir.
  • Em celulares, vira um bottom sheet que pode ser deslizado para baixo para cancelar. Enquanto uma ação está pendente, deslizar, Esc e Cancel ficam bloqueados e os botões continuam focáveis.
  • Com movimento reduzido ativado, ele aparece com fade em vez de escala.

Construído sobre o drawer do Base UI, por meio de <Sheet />. Cada parte aceita as props da primitiva ou do elemento que envolve.

PropTipoPadrão
open
boolean–
defaultOpen
booleanfalse
onOpenChangeNão é chamado para fechamentos bloqueados por uma ação pendente.
(open: boolean, details) => void–
onOpenChangeCompleteChamado após a animação de abertura ou fechamento.
(open: boolean) => void–
disablePointerDismissalMantém o diálogo aberto ao clicar no backdrop.
booleantrue
handleConecta gatilhos desanexados.
AlertDialogHandle<Payload>–
actionsRefFecha ou desmonta o diálogo de forma imperativa.
RefObject<{ close, unmount }>–
children
ReactNode | ({ payload }) => ReactNode–
PropTipoPadrão
handle
AlertDialogHandle<Payload>–
payloadRepassado à render function do diálogo.
Payload–
render
ReactElement | (props, state) => ReactElement<button>
AtributoDescrição
data-slot="alert-dialog-trigger"Selecione os gatilhos no CSS.
data-popup-openPresente enquanto o seu diálogo está aberto.
PropTipoPadrão
size
"default" | "sm""default"
initialFocus
boolean | RefObject | (openType) => HTMLElement | booleanCancel button
finalFocus
boolean | RefObject | (closeType) => HTMLElement | booleanThe trigger
dirDefina quando o diálogo deve ser da direita para a esquerda.
"ltr" | "rtl"–
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescrição
data-slot="alert-dialog-content"Seleciona o diálogo no CSS.
data-sizeO tamanho atual.
data-openPresente enquanto aberto.
data-starting-stylePresente enquanto o diálogo anima a entrada.
data-ending-stylePresente enquanto o diálogo anima a saída.
data-nested-drawer-openPresente enquanto um diálogo aninhado está aberto por cima.
data-swipingPresente enquanto está sendo deslizado em um celular.
--nested-drawersQuantos diálogos estão abertos por cima deste.

Um <div> que empilha a mídia, o título e a descrição.

AtributoDescrição
data-slot="alert-dialog-header"Selecione o cabeçalho no CSS.
PropTipoPadrão
variant
"default" | "destructive""default"
AtributoDescrição
data-slot="alert-dialog-media"Seleciona a mídia no CSS.
PropTipoPadrão
render
ReactElement | (props, state) => ReactElement<h2>
AtributoDescrição
data-slot="alert-dialog-title"Selecione o título no CSS.
PropTipoPadrão
renderUse render={<div />} quando ele contiver vários parágrafos.
ReactElement | (props, state) => ReactElement<p>
AtributoDescrição
data-slot="alert-dialog-description"Selecione a descrição no CSS.

Um <div> para os botões. Os botões se empilham em largura total em celulares, e as teclas de seta alternam entre eles.

AtributoDescrição
data-slot="alert-dialog-footer"Seleciona o rodapé no CSS.

Um <Button /> que fecha o diálogo ao ser clicado. Aceita todas as props de Button.

PropTipoPadrão
onClickRetorne uma promise para exibir um spinner e manter o diálogo aberto até ela ser concluída. Chame event.preventDefault() para mantê-lo aberto.
(event) => void | PromiseLike<unknown>–
variant
ButtonVariant"default"
AtributoDescrição
data-slot="alert-dialog-action"Seleciona as ações no CSS.
PropTipoPadrão
variant
ButtonVariant"outline"
size
ButtonSize–
disabledTambém fica desativado enquanto uma ação está pendente.
boolean–
AtributoDescrição
data-slot="alert-dialog-cancel"Seleciona o botão de cancelar no CSS. Ele recebe o foco inicial.
data-disabledPresente enquanto desativado.

<AlertDialogContent /> já renderiza os dois. Use-os apenas ao compor um popup personalizado.

PropTipoPadrão
keepMountedNo portal, mantém o diálogo no DOM enquanto fechado.
booleanfalse

Retorna um handle que conecta elementos <AlertDialogTrigger /> de qualquer lugar da página a um único <AlertDialog />. Tipifique o payload com um genérico: createAlertDialogHandle<{ name: string }>().

Usado em blocos

Blocos que se baseiam em Alert dialog.