Edit profile
Make changes to your profile here. Click save when you're done.
<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="demo-dialog-edit-profile" x-data="dialog(false, true)" x-bind="$main" class="dialog" x-ref="myDialog">
<button type="button" aria-expanded="false" aria-controls="demo-dialog-edit-profile-dialog" x-bind="$trigger" class="btn-outline">Edit Profile</button>
<div role="dialog" id="demo-dialog-edit-profile-dialog" tabindex="-1" aria-modal="true" aria-labelledby="demo-dialog-edit-profile-title" inert x-bind="$content">
<article class="w-full sm:max-w-[425px] max-h-[612px]">
<header>
<h2 id="demo-dialog-edit-profile-title">Edit profile</h2>
<p>Make changes to your profile here. Click save when you're done.</p>
</header>
<section>
<form class="form grid gap-4">
<div class="grid gap-3">
<label for="demo-dialog-edit-profile-name">Name</label>
<input type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" x-ref="focusOnOpen" />
</div>
<div class="grid gap-3">
<label for="demo-dialog-edit-profile-username">Username</label>
<input type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
</div>
</form>
</section>
<footer>
<button class="btn-outline" @click="open = false">Cancel</button>
<button class="btn" @click="open = false">Save changes</button>
</footer>
<button @click="hide()" aria-label="Close dialog">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x">
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
</button>
</article>
</div>
</div>
Usage
HTML + Javascript
This component requires Javascript.
The component is structured as such:
- A
<div class="dialog">
which wraps around the entire component and holds it state (e.g. open/close). - A
<button>
that acts as the trigger to open or close the popover. - A
<div role="dialog">
that holds the dialog itself. It also acts as the backdrop for the dialog. - Inside of it, we have:
- A
<header>
that contains the title (<h2>
) and description (<p>
) of the dialog. - A
<section>
for the content. - A
<footer>
that usually contains actions. - A close
<button>
.
- A
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="demo-dialog-edit-profile" x-data="dialog(false, true)" x-bind="$main" class="dialog" x-ref="myDialog">
<button type="button" aria-expanded="false" aria-controls="demo-dialog-edit-profile-dialog" x-bind="$trigger" class="btn-outline">Edit Profile</button>
<div role="dialog" id="demo-dialog-edit-profile-dialog" tabindex="-1" aria-modal="true" aria-labelledby="demo-dialog-edit-profile-title" inert x-bind="$content">
<article class="w-full sm:max-w-[425px] max-h-[612px]">
<header>
<h2 id="demo-dialog-edit-profile-title">Edit profile</h2>
<p>Make changes to your profile here. Click save when you're done.</p>
</header>
<section>
<form class="form grid gap-4">
<div class="grid gap-3">
<label for="demo-dialog-edit-profile-name">Name</label>
<input type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" x-ref="focusOnOpen" />
</div>
<div class="grid gap-3">
<label for="demo-dialog-edit-profile-username">Username</label>
<input type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
</div>
</form>
</section>
<footer>
<button class="btn-outline" @click="open = false">Cancel</button>
<button class="btn" @click="open = false">Save changes</button>
</footer>
<button @click="hide()" aria-label="Close dialog">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x">
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
</button>
</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>
The component will dispatch dialog:opened
and dialog:closed
events when the dialog is opened or closed. You can also dispatch dialog:open
and dialog:close
events to open or close the dialog programmatically. You will need the element to have an id
to be able to target it (e.g. <div id="my-dialog" class="dialog">
):
<button type="button" x-init @click="$dispatch('dialog:open', { id: 'my-dialog' })">Open #my-dialog</button>
Jinja and Nunjucks
You can use the dialog()
Nunjucks or Jinja macro for this component.
{% set footer %}
<button class="btn-outline" @click="open = false">Cancel</button>
<button class="btn" @click="open = false">Save changes</button>
{% endset %}
{% call dialog(
id="demo-dialog-edit-profile",
title="Edit profile",
description="Make changes to your profile here. Click save when you're done.",
main_attrs={"x-ref": "myDialog"},
trigger="Edit Profile",
trigger_attrs={"class": "btn-outline"},
content_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
footer=footer,
register_on=["alpine:init", "htmx:afterSwap"]
) %}
<form class="form grid gap-4">
<div class="grid gap-3">
<label for="demo-dialog-edit-profile-name">Name</label>
<input type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" />
</div>
<div class="grid gap-3">
<label for="demo-dialog-edit-profile-username">Username</label>
<input type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
</div>
</form>
{% endcall %}
Examples
Scrollable content
Scrollable Content
This is a dialog with scrollable content.
<div id="dialog-example" x-data="dialog(false, true)" x-bind="$main" class="dialog" x-ref="myDialog">
<button type="button" aria-expanded="false" aria-controls="dialog-example-dialog" x-bind="$trigger" class="btn-outline">Scrollable Content</button>
<div role="dialog" id="dialog-example-dialog" tabindex="-1" aria-modal="true" aria-labelledby="dialog-example-title" inert x-bind="$content">
<article class="w-full sm:max-w-[425px] max-h-[612px]">
<header>
<h2 id="dialog-example-title">Scrollable Content</h2>
<p>This is a dialog with scrollable content.</p>
</header>
<section class="overflow-y-auto scrollbar">
<div class="space-y-4 text-sm">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
</section>
<footer>
<button class="btn-outline" @click="open = false">Close</button>
</footer>
<button @click="hide()" aria-label="Close dialog">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x">
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
</button>
</article>
</div>
</div>