Alert Dialog
A question that has to be answered — a native dialog that will not be waved away.
src/components/rahti_ui/alert_dialog.rs
Basic
A trigger, a question, and two answers. The box is a `<dialog role="alertdialog">` opened with `showModal()`, so the focus trap, the inert background and `Escape` are all the browser's — and the two buttons are the only other way out.
shadcn's own demo. `AlertDialogPortal` and `AlertDialogOverlay` are not in it, because both dissolved: a modal `<dialog>` is already in the top layer and `::backdrop` is the overlay. A page selecting on `[data-slot=alert-dialog-overlay]` will find nothing.
Open it and look at where focus lands: on Cancel, not on Continue. That is Radix's behaviour and worth keeping — the safe answer should be the one under the fingers, and `Enter` should not confirm something destructive by accident.
Write the Cancel first and the Action last. The footer is `flex-col-reverse` under `sm`, so the one written last ends up on top when the buttons stack and on the right when they sit in a row — both the conventional place for the action.
Small
`size="sm"` — a narrow box that stays centred at every width and puts its two answers in an even pair of columns. Reach for it when the question is short and the two answers are equally weighted.
`size` is written once, as `data-size` on the box, and four other parts read it back through `group/alert-dialog-content` — the footer becomes a two-column grid, the header stays centred at every width, the title and the media stop rearranging themselves. Not one of them takes a prop for it.
With media
An icon above the question, or beside it. `AlertDialogMedia` is a `size-16` tile that sizes whatever is put in it to `size-8` unless the icon says otherwise.
Narrow the window. On a phone the icon sits above the text and everything is centred; from `sm` up on a `default` box it moves beside the text, the title takes `col-start-2`, and the block goes left-aligned. All of that is the header's grid template changing shape.
The media has to be a real child of the header, and written before the title. The header finds it with `has-data-[slot=alert-dialog-media]:` — there is no context and nothing is passed down, so a media box put anywhere else is just a grey square.
Small, with media
The combination shadcn documents, and the one that shows the size attribute doing real work rather than just setting a width.
The two rules meet here and `sm` wins: the icon stays above the text at every width, because the utility that would move it beside the text is `sm:group-data-[size=default]/…`, and this box is not `default`. One attribute decides it.
Destructive
The case the component exists for. Both the trigger and the action carry the destructive variant, and the media tile is tinted with a `.class(…)` — the one override on this page.
`variant` on the action is a Button variant, because the action *is* a Button — shadcn wraps its primitive in a `<Button asChild>`, so the look is `buttonVariants` rather than a class list of its own. The cancel takes one too, and defaults to `outline`.
The action closes the box, as Radix's does. So the thing being confirmed goes on a handler beside that — `onclick={remove(id)}` runs, and then the script closes. An action that has to keep the box open while it waits for a server wants a Dialog instead; this component is for a question with an immediate answer.
What it refuses
The reason this is a component and not a prop on Dialog.
A Dialog registers a click handler on its box, measures whether the click landed inside, and closes if it did not. This component simply does not register that handler — there is no prop for it, because a dismissible alert dialog is a Dialog.
`Escape` is honoured on purpose. It is the platform's documented way out of a modal and the only one a keyboard user has, and it is unambiguous in a way a stray click beside a question is not. It is intercepted rather than left alone, because the browser's own `Escape` closes a `<dialog>` instantly and the exit animation would never be seen.
`role="alertdialog"` is the other half. `showModal()` implies `role="dialog"`, which is announced as a surface; this is a question, and the role is what says so.
The port
What came from where, what dissolved, and what this file adds.
The class strings are the published registry's — `new-york-v4` — including `AlertDialogMedia` and the `size` prop, which the older shadcn alert dialog did not have. Every one is verbatim.
Two of the twelve exports are gone rather than ported: `AlertDialogPortal` is nothing, because a modal `<dialog>` is already in the top layer, and `AlertDialogOverlay` is `::backdrop`. Worth knowing before reaching for either.
One string was added, the same one `Dialog` adds: `not-open:hidden right-auto bottom-auto`. The first is load-bearing — the UA hides a closed dialog with `display: none`, the box's `grid` is an author rule and beats it, and without this a closed alert dialog would paint in the middle of every page it is on.
The scroll lock is a count on `<body>` under the same key `Dialog` uses, so an alert dialog opened from inside a dialog shares one count with it rather than the two fighting over `overflow`.