Fylgja Confirmation Modal
A confirmation modal asks the reader to make a choice before something continues. It suits moments that need a clear yes or no, like a cookie consent prompt, where the reader should not drift past the question without answering.
Like the Modal, it is built on the native <dialog>
element and opened with an invoker command. Two things set it apart: the buttons
sit in a <form method="dialog">, so pressing one both closes the dialog and
records the reader’s choice, and closedby="none" disables casual dismissal, so
neither Escape nor a backdrop click closes it. The content is classless, with the
two choices sharing the row as equal-width buttons.
Preview
<button type="button" commandfor="cookie-consent" command="show-modal">Cookie settings</button>
<dialog
id="cookie-consent"
closedby="none"
aria-labelledby="cookie-consent-title"
style="--my: auto var(--size-4); --md_my: auto var(--size-8)"
>
<form method="dialog" class="flow">
<h2 id="cookie-consent-title">Accept cookies?</h2>
<p>We use cookies to improve your experience and analyze site traffic. Do you accept?</p>
<div class="flex-wrap gap">
<button value="decline" class="flex-auto">Decline</button>
<button value="accept" class="flex-auto">Accept</button>
</div>
</form>
</dialog><div x-data="{ open: false }">
<button type="button" @click="open = true">Cookie settings</button>
<dialog
x-show="open"
x-htmldialog.noscroll.closeby.none="open = false"
aria-labelledby="cookie-title"
class="flow"
style="--my: auto var(--size-4); --md_my: auto var(--size-8)"
>
<h2 id="cookie-title">Accept cookies?</h2>
<p>We use cookies to improve your experience and analyze site traffic. Do you accept?</p>
<div class="flex-wrap gap">
<button type="button" @click="open = false" class="flex-auto">Decline</button>
<button type="button" @click="open = false /* store consent here */" class="flex-auto">Accept</button>
</div>
</dialog>
</div>Usage
- A trigger button with
commandforandcommand="show-modal"opens the dialog, no JavaScript required. - Inside the dialog, a
<form method="dialog">wraps the question and the two choices. Each choice is a<button>with avalue. - Pressing either button submits the form, which closes the dialog and stores the
pressed button’s
valueondialog.returnValue.
The two choices sit in a <div class="flex-wrap gap">, and each button gets
flex-auto so they share the row as equal-width actions and wrap on narrow
screens. Give each choice a clear, specific label (“Accept”, “Decline”) rather
than a generic “OK”, so the answer is unambiguous.
closedby="none" stops the dialog closing on Escape or a backdrop click, so the
reader cannot skip past the question without answering. Because you are removing
the usual escape hatch, make sure one option is a safe, non-committal choice (here,
Decline), and reserve this pattern for cases where a decision is genuinely
required. For an everyday, freely dismissible dialog, use the plain
Modal.
The Alpine version
The Alpine tab above opens the same <dialog> from Alpine state with
x-htmldialog. That directive comes from the
AlpineJS Dialog plugin, whose docs cover
installing it and the modifiers it takes.
Reacting to the choice
Closing the dialog is declarative, but acting on the answer needs a small
<script>. Listen for the close event and check returnValue.
<script>
const dialog = document.getElementById("cookie-consent");
dialog.addEventListener("close", () => {
if (dialog.returnValue === "accept") {
// Enable analytics and store the consent cookie here.
}
});
</script>
Anatomy
| Part | Responsibility |
|---|---|
Trigger <button command> |
Opens the dialog via command="show-modal" and commandfor. |
<dialog> |
The container, rendered in the top layer over an inert page. |
<form method="dialog"> |
Closes the dialog on submit and reports the chosen value. |
<button value> |
One choice. Its value becomes dialog.returnValue. |
.flex-wrap.gap + .flex-auto |
Row wrapper; flex-auto makes the choices equal-width. |
Accessibility
- Opened as a modal, the dialog makes the rest of the page inert and traps focus.
- On close, focus returns to the trigger automatically.
closedby="none"intentionally disables Escape and backdrop dismissal, so the reader has to pick an option. Make sure one of them is a safe, non-committal choice (here, Decline), since it is the only way to leave without opting in.- Label the dialog with
aria-labelledbypointing at the heading, and make the question state plainly what each choice does.
FAQ
How is this different from a plain modal?
A plain Modal shows content the reader simply dismisses. A confirmation modal asks a yes or no question, such as a cookie consent, and reports which choice was made. Use it when the reader has to decide before continuing.
How does the modal know which button was pressed?
The buttons live inside a <form method="dialog"> and each has a value. Submitting the form closes the dialog and stores that value on the dialog's returnValue, which you can read in a close event. See Reacting to the choice.
Do I need JavaScript for the buttons to close the dialog?
No. Buttons inside a <form method="dialog"> close the dialog when pressed, with no script. JavaScript is only needed to react to the choice after the dialog closes.
What does the Alpine version need to work?
The x-htmldialog directive comes from the @fylgja/alpinejs-dialog plugin, which makes x-show call showModal instead of toggling display, so you keep the top layer, the backdrop and the focus trap. The example markup leaves the setup out on purpose; install and registration live in the AlpineJS Dialog docs.