Skip to content

Modal

.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

Modal title

This is the modal body. Focus is trapped inside by the browser — no JavaScript needed for that.

Press Escape or click the close button to dismiss.

<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>
const modal = document.getElementById('my-modal')
// Open
modal.showModal()
// Close
modal.close()
// Close on backdrop click
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.close()
})
VariableDefaultDescription
--modal-bgvar(--bp-color-bg-elevated)Modal background
--modal-radiusvar(--bp-radius-xl)Border radius
--modal-paddingvar(--bp-space-8)Inner padding
--modal-width32remMax width

Wide modal

Wide modal

Override --modal-width for wider content like forms or data tables.

<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
  • <dialog> has an implicit dialog ARIA role — no manual role needed.
  • Focus is trapped inside <dialog> natively when opened with showModal().
  • Escape closes the dialog natively — do not suppress this behaviour.
  • Add aria-labelledby pointing to .bp-modal__title for screen readers.
  • The close button must have aria-label="Close".
APIAvailabilityUsed forWithout itPolyfill
<dialog> Widely available Baseline 2022 Native modal, focus trap, Escape key, backdropNeeds full JS implementationdialog-polyfill
@starting-style Newly available Baseline 2024 Fade + slide entrance animationNo entrance animation, modal appears instantlyNone needed
transition: allow-discrete Newly available Baseline 2024 Animate display: none ↔ block on open/closeNo exit animationNone needed
backdrop-filter Widely available Baseline 2022 Blur behind the modal backdropSolid semi-transparent backdropNone needed; @supports guard in CSS
  • --_bg, --_radius, --_padding, --_width — component-private, do not set directly.