Modal
Description
Section titled “Description”.bp-modal wraps the native <dialog> element. The browser provides focus trapping, Escape to close, and accessibility roles automatically. Opening and closing requires two lines of JS (showModal() / close()). The entrance animation uses @starting-style and transition: display allow-discrete — no keyframes needed.
Modal trigger + dialog
<button class="bp-btn" type="button" onclick="document.getElementById('demo-modal').showModal()">Open modal</button>
<dialog class="bp-modal" id="demo-modal" aria-labelledby="demo-modal-title"><div class="bp-modal__header"> <h2 class="bp-modal__title" id="demo-modal-title">Modal title</h2> <button class="bp-modal__close bp-btn bp-btn--icon bp-btn--ghost" type="button" aria-label="Close" onclick="document.getElementById('demo-modal').close()">✕</button></div><div class="bp-modal__body"> <p>This is the modal body. Focus is trapped inside by the browser — no JavaScript needed for that.</p> <p>Press <kbd>Escape</kbd> or click the close button to dismiss.</p></div><div class="bp-modal__footer"> <button class="bp-btn bp-btn--ghost" type="button" onclick="document.getElementById('demo-modal').close()">Cancel</button> <button class="bp-btn" type="button" onclick="document.getElementById('demo-modal').close()">Confirm</button></div></dialog>Opening and closing
Section titled “Opening and closing”const modal = document.getElementById('my-modal')
// Openmodal.showModal()
// Closemodal.close()
// Close on backdrop clickmodal.addEventListener('click', (e) => { if (e.target === modal) modal.close()})Public API
Section titled “Public API”| Variable | Default | Description |
|---|---|---|
--modal-bg | var(--bp-color-bg-elevated) | Modal background |
--modal-radius | var(--bp-radius-xl) | Border radius |
--modal-padding | var(--bp-space-8) | Inner padding |
--modal-width | 32rem | Max width |
Customization example
Section titled “Customization example”Wide modal
<style>.demo-modal--wide { --modal-width: 48rem; }</style><button class="bp-btn" type="button" onclick="document.getElementById('wide-modal').showModal()">Open wide modal</button>
<dialog class="bp-modal demo-modal--wide" id="wide-modal" aria-labelledby="wide-modal-title"><div class="bp-modal__header"> <h2 class="bp-modal__title" id="wide-modal-title">Wide modal</h2> <button class="bp-modal__close bp-btn bp-btn--icon bp-btn--ghost" type="button" aria-label="Close" onclick="document.getElementById('wide-modal').close()">✕</button></div><div class="bp-modal__body"> <p>Override <code>--modal-width</code> for wider content like forms or data tables.</p></div><div class="bp-modal__footer"> <button class="bp-btn" type="button" onclick="document.getElementById('wide-modal').close()">Done</button></div></dialog> ✓ No axe violations tested 2026-05-11
WCAG criteria covered:
Accessibility
Section titled “Accessibility”<dialog>has an implicitdialogARIA role — no manualroleneeded.- Focus is trapped inside
<dialog>natively when opened withshowModal(). Escapecloses the dialog natively — do not suppress this behaviour.- Add
aria-labelledbypointing to.bp-modal__titlefor screen readers. - The close button must have
aria-label="Close".
Browser APIs
Section titled “Browser APIs”| API | Availability | Used for | Without it | Polyfill |
|---|---|---|---|---|
<dialog> | Widely available Baseline 2022 | Native modal, focus trap, Escape key, backdrop | Needs full JS implementation | dialog-polyfill |
@starting-style | Newly available Baseline 2024 | Fade + slide entrance animation | No entrance animation, modal appears instantly | None needed |
transition: allow-discrete | Newly available Baseline 2024 | Animate display: none ↔ block on open/close | No exit animation | None needed |
backdrop-filter | Widely available Baseline 2022 | Blur behind the modal backdrop | Solid semi-transparent backdrop | None needed; @supports guard in CSS |
Internals
Section titled “Internals”--_bg,--_radius,--_padding,--_width— component-private, do not set directly.