Zero-dependency modals built on the native
<dialog> element — top layer, focus handled, promises
included. Click anything below — every card is live.
<script src="https://cdn.jsdelivr.net/gh/vanilla-ui-kit/components/modal/modal.js"></script>
Title, text content, a footer — Esc, backdrop, and ✕ all close it.
Modal.open({
title: 'Welcome aboard',
content: 'Plain string, rendered as text.',
buttons: [{ label: 'Got it', variant: 'primary' }]
})
Four widths; full takes over the viewport.
Modal.open({ title: 'Large', size: 'lg', … })
Promises resolve to boolean / string|null —
the result lands below.
Modal.confirm('Delete this file?')
.then(ok => ok && remove())
Modal.prompt({ message: 'New name:',
value: 'untitled.txt' })
.then(name => name !== null && rename(name))
Variants, close values, and onClick returning
false to keep it open.
buttons: [
{ label: 'Discard', variant: 'danger', close: 'discarded' },
{ label: 'Keep editing', onClick: () => false },
{ label: 'Save', variant: 'primary', close: 'saved' }
]
No JS: data-vmd-open on the trigger,
data-vmd-close inside. The hidden element is adopted and
put back on close.
<button data-vmd-open="#newsletter-modal">…</button>
<div id="newsletter-modal" hidden
data-vmd-title="Stay in the loop">
…
<button data-vmd-close>Maybe later</button>
</div>
No Esc, no backdrop click, no ✕ — only an explicit choice closes it.
Modal.open({
title: 'Session expired',
dismissible: false,
buttons: [{ label: 'Sign in', variant: 'primary' }]
})