Are you absolutely sure?
This action cannot be undone. This will permanently delete your account and remove your data from our servers.
<script>
window.basecoat = window.basecoat || {};
window.basecoat.registerDialog = function (Alpine) {
if (Alpine.components && Alpine.components.dialog) return;
Alpine.data("dialog", (initialOpen = false, initialCloseOnOverlayClick = true) => ({
id: null,
open: false,
closeOnOverlayClick: true,
init() {
this.id = this.$el.id;
if (!this.id) {
console.warn("Dialog component initialized without an `id`. This may cause issues with event targeting and accessibility.");
}
this.open = initialOpen;
this.closeOnOverlayClick = initialCloseOnOverlayClick;
},
show() {
if (!this.open) {
this.open = true;
this.$nextTick(() => {
this.$el.dispatchEvent(new CustomEvent("dialog:opened", { bubbles: true, detail: { id: this.id } }));
setTimeout(() => {
const focusTarget = this.$refs.focusOnOpen || this.$el.querySelector('[role="dialog"]');
if (focusTarget) focusTarget.focus();
}, 50);
});
}
},
hide() {
if (this.open) {
this.open = false;
this.$nextTick(() => {
this.$el.dispatchEvent(new CustomEvent("dialog:closed", { bubbles: true, detail: { id: this.id } }));
});
}
},
$main: {
"@dialog:open.window"(e) {
if (e.detail && e.detail.id === this.id) this.show();
},
"@dialog:close.window"(e) {
if (e.detail && e.detail.id === this.id) this.hide();
},
"@keydown.escape.window"() {
this.open && this.hide();
},
},
$trigger: {
"@click"() {
this.show();
},
":aria-expanded"() {
return this.open;
},
},
$content: {
":inert"() {
return !this.open;
},
"@click.self"() {
if (this.closeOnOverlayClick) this.hide();
},
"x-cloak": "",
},
}));
};
document.addEventListener("alpine:init", () => {
window.basecoat.registerDialog(Alpine);
});
</script>
<div id="alert-dialog" x-data="dialog(false, false)" x-bind="$main" class="dialog">
<button type="button" aria-expanded="false" aria-controls="alert-dialog-dialog" x-bind="$trigger" class="btn-outline">Open alert dialog</button>
<div role="dialog" id="alert-dialog-dialog" tabindex="-1" aria-modal="true" aria-labelledby="alert-dialog-title" inert x-bind="$content">
<article>
<header>
<h2 id="alert-dialog-title">Are you absolutely sure?</h2>
<p>This action cannot be undone. This will permanently delete your account and remove your data from our servers.</p>
</header>
<footer>
<button class="btn-outline" @click="open = false">Cancel</button>
<button class="btn-primary" @click="open = false">Continue</button>
</footer>
</article>
</div>
</div>
Usage
HTML + Javascript
This component requires Javascript.
The Alert Dialog component uses the same Alpine.js code as the Dialog component with one small difference: we set initialCloseOnOverlayClick
to false
to prevent the dialog from closing when the overlay is clicked.
You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. x-bind
, x-data
, @click
).
<div id="alert-dialog" x-data="dialog(false, false)" x-bind="$main" class="dialog">
<button type="button" aria-expanded="false" aria-controls="alert-dialog-dialog" x-bind="$trigger" class="btn-outline">Open alert dialog</button>
<div role="dialog" id="alert-dialog-dialog" tabindex="-1" aria-modal="true" aria-labelledby="alert-dialog-title" inert x-bind="$content">
<article>
<header>
<h2 id="alert-dialog-title">Are you absolutely sure?</h2>
<p>This action cannot be undone. This will permanently delete your account and remove your data from our servers.</p>
</header>
<footer>
<button class="btn-outline" @click="open = false">Cancel</button>
<button class="btn-primary" @click="open = false">Continue</button>
</footer>
</article>
</div>
</div>
<script>
window.basecoat = window.basecoat || {};
window.basecoat.registerDialog = function (Alpine) {
if (Alpine.components && Alpine.components.dialog) return;
Alpine.data("dialog", (initialOpen = false, initialCloseOnOverlayClick = true) => ({
id: null,
open: false,
closeOnOverlayClick: true,
init() {
this.id = this.$el.id;
if (!this.id) {
console.warn("Dialog component initialized without an `id`. This may cause issues with event targeting and accessibility.");
}
this.open = initialOpen;
this.closeOnOverlayClick = initialCloseOnOverlayClick;
},
show() {
if (!this.open) {
this.open = true;
this.$nextTick(() => {
this.$el.dispatchEvent(new CustomEvent("dialog:opened", { bubbles: true, detail: { id: this.id } }));
setTimeout(() => {
const focusTarget = this.$refs.focusOnOpen || this.$el.querySelector('[role="dialog"]');
if (focusTarget) focusTarget.focus();
}, 50);
});
}
},
hide() {
if (this.open) {
this.open = false;
this.$nextTick(() => {
this.$el.dispatchEvent(new CustomEvent("dialog:closed", { bubbles: true, detail: { id: this.id } }));
});
}
},
$main: {
"@dialog:open.window"(e) {
if (e.detail && e.detail.id === this.id) this.show();
},
"@dialog:close.window"(e) {
if (e.detail && e.detail.id === this.id) this.hide();
},
"@keydown.escape.window"() {
this.open && this.hide();
},
},
$trigger: {
"@click"() {
this.show();
},
":aria-expanded"() {
return this.open;
},
},
$content: {
":inert"() {
return !this.open;
},
"@click.self"() {
if (this.closeOnOverlayClick) this.hide();
},
"x-cloak": "",
},
}));
};
document.addEventListener("alpine:init", () => {
window.basecoat.registerDialog(Alpine);
});
</script>
Jinja and Nunjucks
You can use the dialog()
Nunjucks or Jinja macro for this component. If you use one of the macros, make sure to set close_on_overlay_click
and close_button
to False
(or false
for Nunjucks).
{% set footer %}
<button class="btn-outline" @click="open = false">Cancel</button>
<button class="btn-primary" @click="open = false">Continue</button>
{% endset %}
{{ dialog(
id="demo-alert-dialog",
title="Are you absolutely sure?",
description="This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
trigger="Open Alert Dialog",
trigger_attrs={"class": "btn-outline"},
footer=footer,
close_button=False,
close_on_overlay_click=False
) }}