Create modals using the <dialog> element.
See examples with custom transitions:
[^slide-right]: Consider setting scrollbar-gutter: stable; on the HTML element to avoid layout shift during transition.
Requires Svelte v5 and runes mode.
$state(boolean)<dialog> element with focus trap and Esc supportoncancel and onclose event handlers are supportedTo upgrade from previous versions, see the migration guide.
pnpm add svelte-html-modal -D
npm i svelte-html-modal -D
/* Add to your global CSS file (e.g. src/routes/layout.css) */
/* For Tailwind v4 users, place it inside `@layer base` */
/* See https://tailwindcss.com/docs/adding-custom-styles */
body:has(dialog:modal) {
overflow: hidden;
touch-action: none;
}
<!-- See src/routes/docs/+page.svelte -->
<script>
import { Modal } from 'svelte-html-modal';
// Client-side JavaScript is required to display the modal.
// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal
let isOpen = $state(false);
const open = () => (isOpen = true);
const close = () => (isOpen = false);
</script>
<button type="button" onclick={open}>Open Modal</button>
<!-- The wrapper <div> is used for styling. -->
<!-- See the <style> element below. -->
<div class="modal-wrapper">
<Modal bind:isOpen closeOnBackdropClick={true} closeOnEscapeKey={true}>
<strong>Close the Modal</strong>
<ul>
<li>Click on the backdrop</li>
<li>Press the <kbd>Esc</kbd> key</li>
<li>
<button type="button" onclick={close}>JavaScript Button</button>
</li>
<li>
<!-- See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#method -->
<form method="dialog">
<button>Submit Button</button>
</form>
</li>
</ul>
</Modal>
</div>
<!-- Vanilla CSS -->
<style lang="postcss">
/* Style <dialog> within the .modal-wrapper element. */
/* See https://svelte.dev/docs/svelte/scoped-styles */
.modal-wrapper :global {
> dialog {
width: 20rem;
padding: 1rem;
border-radius: 0.375rem;
&:modal {
overscroll-behavior: contain;
}
&::backdrop {
backdrop-filter: blur(8px) brightness(0.5);
}
}
}
</style>
Tailwind CSS v4 class names can be used as well:
<Modal
class="m-auto w-80 rounded-md p-4 backdrop:backdrop-blur backdrop:backdrop-brightness-50 open:overscroll-contain"
></Modal>
[!NOTE]
For fullscreen modal, override max-size values:
dialog {
/* Override user-agent dialog:modal max-sizes. */
max-height: 100%; /* calc((100% - 6px) - 2em); */
max-width: 100%; /* calc((100% - 6px) - 2em); */
}
{
"closeOnBackdropClick": false,
"closeOnEscapeKey": true
}
type Props = {
dialog?: HTMLDialogElement | undefined; // bindable
isOpen: boolean; // bindable
closeOnBackdropClick?: boolean | undefined;
closeOnEscapeKey?: boolean | undefined;
class?: HTMLDialogAttributes['class'] | undefined;
children?: Snippet | undefined;
} & Partial<Pick<HTMLDialogAttributes, 'id' | 'oncancel' | 'onclose'>>;
[!IMPORTANT]
ThecloseOnEscapeKeyprop has a known issue in Chrome 126~133. The modal can be closed by pressing theEsckey twice. This will be resolved in a future update when theclosedbyattribute is implemented. Learn more