Skip to documentation
BoobstrapDocs
Components

Dialogs

Build accessible modal dialogs with optional regions, flexible sizing, scrollable bodies, forms, triggers, and framework adapters.

Components guideCopy-ready referencev0.7.0 current
Components

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.

Use a dialog for focused, interruptive work.

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.

Edit profile

Update the information visible to your workspace.

HTML · Form dialog
<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">&times;</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.

Archive this project?

Members will lose access until the project is restored.

The project and its history remain available to workspace administrators.

HTML · Static-backdrop confirmation
<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.

Release checklist

  • Accessibility review
  • Responsive layout review
  • Cross-browser verification
  • Migration notes
  • Package integrity
  • Security scan
  • Performance budget
  • Rollback plan
  • Stakeholder approval
  • Production monitoring
HTML · Scrollable dialog body
<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">&times;</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.

Small dialog

Default dialog

Large, tall dialog

Fullscreen dialog

HTML · Dialog sizes
<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.

JavaScript · Imperative control
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.

HTML · Alpine.js
<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">&times;</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.

JSX · React dialog
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()}>&times;</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.

Vue · Dialog composable
<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()">&times;</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

ClassPurpose
.bs-dialogCreates the centered native modal surface and defines its width and maximum height.
.bs-alert-dialog / .bs-alert-dialog-danger / .bs-alert-dialog-successConstrains a focused confirmation and selects contextual icon treatment.
.bs-alert-dialog-iconPlaces a compact contextual icon beside the alert-dialog title and description.
.bs-dialog-headerOptional fixed header grid for the title, description, and close control.
.bs-dialog-titleNames the dialog and connects through aria-labelledby.
.bs-dialog-descriptionOptional supporting copy connected through aria-describedby.
.bs-dialog-closeOptional icon-sized close control placed in the header.
.bs-dialog-bodyFlexible content region that scrolls independently when content exceeds the available height.
.bs-dialog-footerOptional fixed, wrapping footer for task actions.
.bs-dialog-sm / .bs-dialog-lg / .bs-dialog-xlSelects 24rem, 48rem, or 64rem width; the unmodified dialog is 36rem.
.bs-dialog-height-sm / .bs-dialog-height-lgConstrains the dialog to a 24rem or 48rem responsive height.
.bs-dialog-fullscreenExpands the dialog to the viewport inset while preserving its regions.
.bs-dialog-openLocks document scrolling while a controlled modal or drawer is open.

Behavior and customization

ContractPurpose
data-bs-dialogInitializes the native dialog with Boobstrap behavior.
data-bs-toggle="dialog"Opens or closes the dialog named by aria-controls.
data-bs-dialog-dismissCloses from an explicit button inside the dialog.
data-bs-dialog-close-on-backdrop="false"Keeps outside clicks from discarding the current task.
--bs-dialog-widthOverrides the modal width at the component boundary.
--bs-dialog-max-heightOverrides the responsive maximum height before the body scrolls.
show() / hide() / toggle()Controls the same instance imperatively from application JavaScript.
bs:dialog:show / bs:dialog:hideCancelable before-events for application coordination.
bs:dialog:shown / bs:dialog:hiddenCompleted events emitted after visible state and focus are synchronized.