Dialogs
Build accessible modal dialogs with optional regions, flexible sizing, scrollable bodies, forms, triggers, and framework adapters.
Dialogs
Dialogs use the native <dialog> element for modal semantics, focus containment, and background inertness. Compose an optional header and footer around the independently scrolling body, then choose a size or override the component properties.
Keep routine content in the page. A modal should ask for a decision, collect a short form, or present a task that must be completed or dismissed before returning to the page.
Form dialog with complete structure
The trigger names the native dialog with aria-controls. Header and footer remain visible while a longer form scrolls inside the body.
<button class="bs-btn bs-btn-primary" type="button" data-bs-toggle="dialog" aria-controls="profile-dialog">Edit profile</button>
<dialog class="bs-dialog bs-dialog-lg" id="profile-dialog" data-bs-dialog aria-labelledby="profile-dialog-title" aria-describedby="profile-dialog-description">
<header class="bs-dialog-header">
<h4 class="bs-dialog-title" id="profile-dialog-title">Edit profile</h4>
<p class="bs-dialog-description" id="profile-dialog-description">Update the information visible to your workspace.</p>
<button class="bs-dialog-close" type="button" data-bs-dialog-dismiss aria-label="Close profile dialog">×</button>
</header>
<div class="bs-dialog-body">
<form class="bs-stack bs-gap-4">
<div class="bs-form-group"><label class="bs-label" for="dialog-display-name">Display name</label><input class="bs-input" id="dialog-display-name" value="Ada Lovelace" /></div>
<div class="bs-form-group"><label class="bs-label" for="dialog-role">Role</label><select class="bs-select" id="dialog-role"><option>Engineering lead</option><option>Product designer</option></select></div>
<div class="bs-form-group"><label class="bs-label" for="dialog-bio">Bio</label><textarea class="bs-textarea" id="dialog-bio" rows="4">Building dependable tools for ambitious teams.</textarea></div>
</form>
</div>
<footer class="bs-dialog-footer">
<button class="bs-btn bs-btn-secondary" type="button" data-bs-dialog-dismiss>Cancel</button>
<button class="bs-btn bs-btn-primary" type="button">Save changes</button>
</footer>
</dialog>Focused confirmation without a header close button
Header controls and footers are optional independently. This destructive decision disables backdrop dismissal so an accidental outside click cannot discard the choice.
<button class="bs-btn bs-btn-secondary" type="button" data-bs-toggle="dialog" aria-controls="archive-dialog">Archive project</button>
<dialog class="bs-dialog bs-alert-dialog bs-alert-dialog-danger" id="archive-dialog" role="alertdialog" data-bs-dialog data-bs-dialog-close-on-backdrop="false" aria-labelledby="archive-dialog-title" aria-describedby="archive-dialog-description">
<header class="bs-dialog-header">
<span class="bs-alert-dialog-icon" aria-hidden="true"><i data-lucide="triangle-alert" class="bs-icon" aria-hidden="true"></i></span>
<h4 class="bs-dialog-title" id="archive-dialog-title">Archive this project?</h4>
<p class="bs-dialog-description" id="archive-dialog-description">Members will lose access until the project is restored.</p>
</header>
<div class="bs-dialog-body"><p class="bs-mb-0">The project and its history remain available to workspace administrators.</p></div>
<footer class="bs-dialog-footer">
<button class="bs-btn bs-btn-secondary" type="button" data-bs-dialog-dismiss>Keep project</button>
<button class="bs-btn bs-btn-primary" type="button" data-bs-dialog-dismiss>Archive project</button>
</footer>
</dialog>Constrained height with a scrolling body
A height modifier makes overflow behavior easy to verify: the body reaches every item without moving the title or footer actions.
<button class="bs-btn bs-btn-secondary" type="button" data-bs-toggle="dialog" aria-controls="release-dialog">Review release</button>
<dialog class="bs-dialog bs-dialog-height-sm" id="release-dialog" data-bs-dialog aria-labelledby="release-dialog-title">
<header class="bs-dialog-header">
<h4 class="bs-dialog-title" id="release-dialog-title">Release checklist</h4>
<button class="bs-dialog-close" type="button" data-bs-dialog-dismiss aria-label="Close release checklist">×</button>
</header>
<div class="bs-dialog-body">
<ul class="bs-checklist"><li>Accessibility review</li><li>Responsive layout review</li><li>Cross-browser verification</li><li>Migration notes</li><li>Package integrity</li><li>Security scan</li><li>Performance budget</li><li>Rollback plan</li><li>Stakeholder approval</li><li>Production monitoring</li></ul>
</div>
<footer class="bs-dialog-footer"><button class="bs-btn bs-btn-primary" type="button" data-bs-dialog-dismiss>Checklist complete</button></footer>
</dialog>Width and height choices
Choose a named size for common layouts or override the component properties locally. Fullscreen remains inset from the viewport so its boundary stays visible.
<div class="bs-flex bs-flex-wrap bs-gap-3">
<button class="bs-btn bs-btn-secondary bs-btn-sm" type="button" data-bs-toggle="dialog" aria-controls="dialog-size-small">Small</button>
<button class="bs-btn bs-btn-secondary bs-btn-sm" type="button" data-bs-toggle="dialog" aria-controls="dialog-size-default">Default</button>
<button class="bs-btn bs-btn-secondary bs-btn-sm" type="button" data-bs-toggle="dialog" aria-controls="dialog-size-large">Large + tall</button>
<button class="bs-btn bs-btn-secondary bs-btn-sm" type="button" data-bs-toggle="dialog" aria-controls="dialog-size-full">Fullscreen</button>
</div>
<dialog class="bs-dialog bs-dialog-sm" id="dialog-size-small" data-bs-dialog aria-labelledby="dialog-size-small-title"><div class="bs-dialog-body"><h4 id="dialog-size-small-title">Small dialog</h4><button class="bs-btn bs-btn-primary bs-mt-4" type="button" data-bs-dialog-dismiss>Done</button></div></dialog>
<dialog class="bs-dialog" id="dialog-size-default" data-bs-dialog aria-labelledby="dialog-size-default-title"><div class="bs-dialog-body"><h4 id="dialog-size-default-title">Default dialog</h4><button class="bs-btn bs-btn-primary bs-mt-4" type="button" data-bs-dialog-dismiss>Done</button></div></dialog>
<dialog class="bs-dialog bs-dialog-lg bs-dialog-height-lg" id="dialog-size-large" data-bs-dialog aria-labelledby="dialog-size-large-title"><div class="bs-dialog-body"><h4 id="dialog-size-large-title">Large, tall dialog</h4></div><footer class="bs-dialog-footer"><button class="bs-btn bs-btn-primary" type="button" data-bs-dialog-dismiss>Done</button></footer></dialog>
<dialog class="bs-dialog bs-dialog-fullscreen" id="dialog-size-full" data-bs-dialog aria-labelledby="dialog-size-full-title"><div class="bs-dialog-body"><h4 id="dialog-size-full-title">Fullscreen dialog</h4></div><footer class="bs-dialog-footer"><button class="bs-btn bs-btn-primary" type="button" data-bs-dialog-dismiss>Done</button></footer></dialog>Control a dialog with JavaScript
The aggregate initializer discovers data-bs-dialog automatically, or import one controller when application code needs an imperative instance.
import { Dialog } from "@boobstrap/boobstrap/js/dialog";
const element = document.querySelector("#profile-dialog");
const dialog = Dialog.getOrCreateInstance(element);
dialog.show();
dialog.hide();
dialog.toggle();
element.addEventListener("bs:dialog:hide", (event) => {
if (document.body.dataset.unsaved === "true") event.preventDefault();
});Let Alpine own dialog state
The official provider works with standard Alpine and the strict-CSP build. Bind the trigger, native dialog, and any dismiss controls without attaching the vanilla controller.
<div x-data="bsDialog">
<button class="bs-btn" type="button" x-bind="trigger" aria-controls="alpine-dialog">Open dialog</button>
<dialog class="bs-dialog" id="alpine-dialog" x-ref="dialog" x-bind="panel" aria-labelledby="alpine-dialog-title">
<header class="bs-dialog-header">
<h2 class="bs-dialog-title" id="alpine-dialog-title">Account details</h2>
<button class="bs-dialog-close" type="button" x-bind="dismiss" aria-label="Close account details">×</button>
</header>
<div class="bs-dialog-body">...</div>
</dialog>
</div>React
useDialog synchronizes a native dialog with React state, handles Escape, restores trigger focus, and composes consumer handlers. Spread dialog props onto <dialog> itself.
import { useDialog } from "@boobstrap/react";
export function AccountDialog() {
const dialog = useDialog({ id: "react-account-dialog" });
return (
<>
<button className="bs-btn" {...dialog.getTriggerProps()}>Open dialog</button>
<dialog className="bs-dialog" aria-labelledby="react-account-title"
{...dialog.getDialogProps()}>
<header className="bs-dialog-header">
<h2 className="bs-dialog-title" id="react-account-title">Account details</h2>
<button className="bs-dialog-close" aria-label="Close account details"
{...dialog.getDismissProps()}>×</button>
</header>
<div className="bs-dialog-body">Profile and security settings.</div>
</dialog>
</>
);
}Vue
useDialog exposes the same native-dialog and focus contract through computed state and prop objects. Bind the returned props to the trigger, dialog, and every explicit dismiss button.
<script setup>
import { useDialog } from "@boobstrap/vue";
const dialog = useDialog({ id: "vue-account-dialog" });
</script>
<template>
<button class="bs-btn" v-bind="dialog.getTriggerProps()">Open dialog</button>
<dialog class="bs-dialog" aria-labelledby="vue-account-title"
v-bind="dialog.getDialogProps()">
<header class="bs-dialog-header">
<h2 class="bs-dialog-title" id="vue-account-title">Account details</h2>
<button class="bs-dialog-close" aria-label="Close account details"
v-bind="dialog.getDismissProps()">×</button>
</header>
<div class="bs-dialog-body">Profile and security settings.</div>
</dialog>
</template>Read the React adapter guide or Vue adapter guide for controlled state, SSR, and shared lifecycle rules.
Semantics
Use a native <dialog>, connect a visible title with aria-labelledby, and connect supporting copy with aria-describedby.
Dismissal
Keep Escape and at least one explicit dismiss control. Disable backdrop dismissal for destructive decisions or forms where an outside click could lose work.
Scrolling
Place overflow-prone content only in .bs-dialog-body. Header and footer are fixed flex regions, so do not add competing page-level scroll containers.
State ownership
Use Boobstrap JS or an adapter on one instance, never both. All layers expose the same open/close state and lifecycle events.
Dialog class API
| Class | Purpose |
|---|---|
.bs-dialog | Creates the centered native modal surface and defines its width and maximum height. |
.bs-alert-dialog / .bs-alert-dialog-danger / .bs-alert-dialog-success | Constrains a focused confirmation and selects contextual icon treatment. |
.bs-alert-dialog-icon | Places a compact contextual icon beside the alert-dialog title and description. |
.bs-dialog-header | Optional fixed header grid for the title, description, and close control. |
.bs-dialog-title | Names the dialog and connects through aria-labelledby. |
.bs-dialog-description | Optional supporting copy connected through aria-describedby. |
.bs-dialog-close | Optional icon-sized close control placed in the header. |
.bs-dialog-body | Flexible content region that scrolls independently when content exceeds the available height. |
.bs-dialog-footer | Optional fixed, wrapping footer for task actions. |
.bs-dialog-sm / .bs-dialog-lg / .bs-dialog-xl | Selects 24rem, 48rem, or 64rem width; the unmodified dialog is 36rem. |
.bs-dialog-height-sm / .bs-dialog-height-lg | Constrains the dialog to a 24rem or 48rem responsive height. |
.bs-dialog-fullscreen | Expands the dialog to the viewport inset while preserving its regions. |
.bs-dialog-open | Locks document scrolling while a controlled modal or drawer is open. |
Behavior and customization
| Contract | Purpose |
|---|---|
data-bs-dialog | Initializes the native dialog with Boobstrap behavior. |
data-bs-toggle="dialog" | Opens or closes the dialog named by aria-controls. |
data-bs-dialog-dismiss | Closes from an explicit button inside the dialog. |
data-bs-dialog-close-on-backdrop="false" | Keeps outside clicks from discarding the current task. |
--bs-dialog-width | Overrides the modal width at the component boundary. |
--bs-dialog-max-height | Overrides the responsive maximum height before the body scrolls. |
show() / hide() / toggle() | Controls the same instance imperatively from application JavaScript. |
bs:dialog:show / bs:dialog:hide | Cancelable before-events for application coordination. |
bs:dialog:shown / bs:dialog:hidden | Completed events emitted after visible state and focus are synchronized. |