Alert Dialog
Modalne okno potwierdzenia, które zatrzymuje użytkownika przed akcją o poważnych lub nieodwracalnych skutkach. Zamknąć je można tylko, dokonując wyboru.
"use client"
import * as React from "react"
import { Trash2Icon } from "lucide-react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
export default function AlertDialogDemo() {
const [open, setOpen] = React.useState(false)
return (
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogTrigger render={<Button variant="outline" />}>
<Trash2Icon data-icon="inline-start" />
Usuń projekt
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogMedia variant="destructive">
<Trash2Icon />
</AlertDialogMedia>
<AlertDialogTitle>Usunąć projekt „Atlas”?</AlertDialogTitle>
<AlertDialogDescription>
Projekt, jego 142 zgłoszenia i wszystkie wdrożenia zostaną trwale usunięte. Tej operacji nie można cofnąć.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Zachowaj projekt</AlertDialogCancel>
<AlertDialogAction
variant="destructive"
onClick={() => setOpen(false)}
>
Usuń projekt
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}Instalacja
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/alert-dialog.jsonUżycie
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"const [open, setOpen] = React.useState(false)
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogTrigger render={<Button variant="outline" />}>Delete project</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete “Atlas”?</AlertDialogTitle>
<AlertDialogDescription>This can’t be undone.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Keep project</AlertDialogCancel>
<AlertDialogAction variant="destructive" onClick={deleteProject}>
Delete project
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>AlertDialogCancel zamyka okno. AlertDialogAction to celowo zwykły Button: potwierdzona operacja często jest asynchroniczna, a okno powinno pozostać otwarte — z akcją w stanie loading — aż do jej powodzenia lub błędu. Kontroluj open i zamknij okno samodzielnie, gdy operacja się zakończy.Przykłady
Podstawowy
Często wystarczą tytuł i opis. Opis mówi, co zostanie utracone, a przyciski — co robi każdy z wyborów.
"use client"
import * as React from "react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
export default function AlertDialogBasic() {
const [open, setOpen] = React.useState(false)
return (
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogTrigger render={<Button variant="ghost" />}>
Odrzuć szkic
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Odrzucić niezapisane zmiany?</AlertDialogTitle>
<AlertDialogDescription>
Ten wpis był edytowany 4 minuty temu. Jeśli teraz wyjdziesz, zmiany przepadną.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Kontynuuj edycję</AlertDialogCancel>
<AlertDialogAction onClick={() => setOpen(false)}>
Odrzuć zmiany
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}Grafika
AlertDialogMedia dodaje ikonę, która nadaje ton, zanim ktokolwiek przeczyta tytuł. Dopasuj jej variant do skutków — destructive przy utracie danych, warning przy zakłóceniach, success przy potwierdzaniu czegoś dobrego, czego nie da się cofnąć, brand przy ważnych, ale bezpiecznych akcjach, default w pozostałych przypadkach — i nadaj przyciskowi akcji ten sam stopień wyróżnienia.
"use client"
import * as React from "react"
import { GitBranchIcon, LogOutIcon, RocketIcon } from "lucide-react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
const confirmations = [
{
trigger: "Wyloguj wszędzie",
media: "default",
icon: LogOutIcon,
title: "Wylogować się ze wszystkich urządzeń?",
description:
"Sesje na 4 urządzeniach, w tym na tym, zostaną zakończone. Aby zalogować się ponownie, potrzebne będzie hasło.",
action: "Wyloguj wszędzie",
actionVariant: "default",
},
{
trigger: "Odłącz GitHuba",
media: "warning",
icon: GitBranchIcon,
title: "Odłączyć GitHuba?",
description:
"Nowe commity przestaną uruchamiać wdrożenia. Istniejące wdrożenia pozostaną dostępne.",
action: "Odłącz",
actionVariant: "default",
},
{
trigger: "Wdróż na produkcję",
media: "brand",
icon: RocketIcon,
title: "Wdrożyć na produkcję?",
description:
"Commit 7f3a2c1 zastąpi wersję produkcyjną dla 12 408 aktywnych użytkowników. Poprzednią wersję możesz przywrócić w każdej chwili.",
action: "Wdróż",
actionVariant: "brand",
},
] as const
function Confirmation({ item }: { item: (typeof confirmations)[number] }) {
const [open, setOpen] = React.useState(false)
const Icon = item.icon
return (
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogTrigger render={<Button variant="outline" />}>
{item.trigger}
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogMedia variant={item.media}>
<Icon />
</AlertDialogMedia>
<AlertDialogTitle>{item.title}</AlertDialogTitle>
<AlertDialogDescription>{item.description}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Anuluj</AlertDialogCancel>
<AlertDialogAction
variant={item.actionVariant}
onClick={() => setOpen(false)}
>
{item.action}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
export default function AlertDialogMediaVariants() {
return (
<div className="flex flex-wrap items-center justify-center gap-3">
{confirmations.map((item) => (
<Confirmation key={item.trigger} item={item} />
))}
</div>
)
}Mały
size="sm" wyśrodkowuje treść i dzieli stopkę na dwa równe przyciski. Używaj go do krótkich pytań z dwiema możliwymi odpowiedziami — o uprawnienia czy szybkie zgody — zwłaszcza na urządzeniach mobilnych.
"use client"
import * as React from "react"
import { BellRingIcon } from "lucide-react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
export default function AlertDialogSmall() {
const [open, setOpen] = React.useState(false)
return (
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogTrigger render={<Button variant="outline" />}>
Włącz alerty
</AlertDialogTrigger>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogMedia variant="brand">
<BellRingIcon />
</AlertDialogMedia>
<AlertDialogTitle>Włączyć alerty o kompilacjach?</AlertDialogTitle>
<AlertDialogDescription>
Powiadomimy cię, gdy wdrożenie się nie powiedzie.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Nie teraz</AlertDialogCancel>
<AlertDialogAction onClick={() => setOpen(false)}>
Włącz
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}Potwierdzenie przez wpisanie
Przy akcjach, które za jednym razem niszczą dużo pracy, poproś o wpisanie nazwy tego, co ma zostać usunięte. Przycisk akcji pozostaje wyłączony, dopóki tekst się nie zgadza, co zapobiega potwierdzaniu z przyzwyczajenia.
"use client"
import * as React from "react"
import { TriangleAlertIcon } from "lucide-react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { Field, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
const workspace = "acme-production"
export default function AlertDialogTypedConfirmation() {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState("")
const matches = value === workspace
return (
<AlertDialog
open={open}
onOpenChange={(next) => {
setOpen(next)
if (!next) setValue("")
}}
>
<AlertDialogTrigger render={<Button variant="destructive" />}>
Usuń przestrzeń roboczą
</AlertDialogTrigger>
<AlertDialogContent>
<form
className="flex flex-col gap-5"
onSubmit={(event) => {
event.preventDefault()
if (matches) setOpen(false)
}}
>
<AlertDialogHeader>
<AlertDialogMedia variant="destructive">
<TriangleAlertIcon />
</AlertDialogMedia>
<AlertDialogTitle>Usunąć tę przestrzeń roboczą?</AlertDialogTitle>
<AlertDialogDescription>
Trwale usuniemy 38 projektów, 214 członków i całą ich historię.
</AlertDialogDescription>
</AlertDialogHeader>
<Field>
<FieldLabel
htmlFor="confirm-workspace"
className="font-normal text-muted-foreground"
>
<span>
Aby potwierdzić, wpisz{" "}
<strong className="font-mono font-medium text-foreground">
{workspace}
</strong>{" "}
w polu poniżej
</span>
</FieldLabel>
<Input
id="confirm-workspace"
value={value}
autoComplete="off"
spellCheck={false}
onChange={(event) => setValue(event.target.value)}
/>
</Field>
<AlertDialogFooter>
<AlertDialogCancel>Anuluj</AlertDialogCancel>
<AlertDialogAction
type="submit"
variant="destructive"
disabled={!matches}
>
Usuń przestrzeń roboczą
</AlertDialogAction>
</AlertDialogFooter>
</form>
</AlertDialogContent>
</AlertDialog>
)
}Akcja asynchroniczna
Nie zamykaj okna, gdy trwa żądanie: przełącz akcję w stan loading, wyłącz anulowanie i ignoruj próby zamknięcia, dopóki operacja się nie zakończy. Potem zamknij okno i potwierdź wynik powiadomieniem Toast.
"use client"
import * as React from "react"
import { KeyRoundIcon } from "lucide-react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { toast } from "@/components/ui/toast"
export default function AlertDialogAsync() {
const [open, setOpen] = React.useState(false)
const [pending, setPending] = React.useState(false)
async function revoke() {
setPending(true)
// Keep the dialog open until the request settles.
await new Promise((resolve) => setTimeout(resolve, 1500))
setPending(false)
setOpen(false)
toast.add({ title: "Unieważniono klucz API", type: "success" })
}
return (
<AlertDialog open={open} onOpenChange={(next) => !pending && setOpen(next)}>
<AlertDialogTrigger render={<Button variant="outline" />}>
Unieważnij klucz
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogMedia variant="warning">
<KeyRoundIcon />
</AlertDialogMedia>
<AlertDialogTitle>Unieważnić klucz „Wdrożenia CI”?</AlertDialogTitle>
<AlertDialogDescription>
Potoki korzystające z tego klucza zakończą się błędem przy następnym uruchomieniu. Nowy klucz możesz utworzyć później.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={pending}>Anuluj</AlertDialogCancel>
<AlertDialogAction
variant="destructive"
loading={pending}
onClick={revoke}
>
Unieważnij klucz
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}<AlertDialog open={open} onOpenChange={(next) => !pending && setOpen(next)}>
{/* … */}
<AlertDialogCancel disabled={pending}>Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive" loading={pending} onClick={revoke}>
Revoke key
</AlertDialogAction>
</AlertDialog>Wytyczne
Kiedy używać
- Przed akcjami nieodwracalnymi: usuwaniem, unieważnianiem, trwałym usuwaniem osób.
- Przed akcjami o szerokim zasięgu skutków: wdrożeniem na produkcję, odłączeniem integracji, wylogowaniem ze wszystkich urządzeń.
- Gdy wyjście oznaczałoby utratę pracy, której nie da się odzyskać.
Kiedy nie używać
- Przy akcjach, które da się cofnąć — wykonaj je od razu i zaproponuj Cofnij w powiadomieniu Toast. Możliwość cofnięcia jest zawsze życzliwsza niż potwierdzenie.
- Do zbierania danych lub wyświetlania informacji — użyj komponentu Dialog.
- Do informowania, że coś poszło nie tak — użyj komponentu Alert w treści strony albo powiadomienia.
Potwierdzaj rzadko
Każde potwierdzenie uczy ludzi odruchowo przeklikiwać kolejne. Zachowaj okna potwierdzenia na nieliczne momenty, które na to zasługują, i mów w nich konkretnie: podaj nazwę, liczbę i skutek.
Nazwij wybory
Tytuł zadaje jedno pytanie. Przycisk akcji powtarza czasownik z tytułu, a przycisk anulowania mówi, co pozostanie bez zmian (Zachowaj projekt), albo jest zwykłym Anuluj, gdy nic bardziej konkretnego nie pasuje. Nigdy nie używaj Tak, Nie ani OK.
Kolejność i wyróżnienie
Umieść akcję na końcu — po prawej od sm wzwyż, na górze, gdy na urządzeniach mobilnych przyciski w stopce układają się jeden pod drugim — a anulowanie przed nią. Przycisku destructive używaj tylko do akcji nieodwracalnych: czerwień oznacza zaraz coś stracisz.
Dostępność
Alert Dialog realizuje wzorzec WAI-ARIA Alert Dialog. Base UI renderuje go z role="alertdialog"; tytuł staje się jego nazwą dostępną, a opis — opisem dostępnym. Dzięki temu czytnik ekranu ogłasza skutki od razu po otwarciu okna.
| Klawisz | Działanie |
|---|---|
EnterSpacja | Na wyzwalaczu: otwiera okno potwierdzenia. |
Tab | Przechodzi między wyborami. Fokus nie może opuścić okna. |
ShiftTab | Przenosi fokus wstecz; z pierwszego elementu przechodzi na ostatni. |
Esc | Zamyka okno — tak samo jak wybór Anuluj — i przywraca fokus na wyzwalacz. |
- Bez zamykania kliknięciem obok. Kliknięcie przyciemnionego tła nie zamyka okna potwierdzenia: trzeba dokonać wyboru. Esc nadal działa jak Anuluj.
- Tytuł i opis są wymagane. Tylko z nich użytkownik czytnika ekranu dowie się, co potwierdza.
- Fokus. Fokus trafia na pierwszy element, który może go przyjąć — zwykle przycisk anulowania, czyli bezpieczny wybór — a po zamknięciu wraca na wyzwalacz.
- Kolor nigdy nie jest jedynym sygnałem. Akcja nieodwracalna jest czerwona, a jej etykieta dodatkowo mówi Usuń.
Dokumentacja API
AlertDialog
Komponent główny. Nie renderuje żadnego elementu. Przyjmuje wszystkie propsy AlertDialog.Root z Base UI — te same co Dialog, bez modal i disablePointerDismissal, które są zawsze włączone.
AlertDialogContent
Renderuje portal, przyciemnione tło i samo okno. Dwa komponenty, z których korzysta — AlertDialogPortal i AlertDialogOverlay — też są eksportowane, na wypadek układów, w których rozmieszczasz je samodzielnie.
AlertDialogMedia
Ikona w kolorowym kółku, od breakpointu sm wzwyż wyświetlana obok tytułu, a w małych oknach i na węższych ekranach — nad nim.
AlertDialogAction
Button z prfct. Przyjmuje wszystkie propsy Button — variant, loading, disabled — i nie zamyka okna.
AlertDialogCancel
Zamyka okno. Renderuje Button z prfct.
AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter
Części układu. AlertDialogTitle renderuje <h2> i nadaje oknu nazwę; AlertDialogDescription renderuje <p> i je opisuje. AlertDialogFooter na małych ekranach układa wybory jeden pod drugim, a od sm wzwyż wyrównuje je do prawej.