Skip to content

Modal Components

Svelte Modals does not provide any modal components, it is only responsible for managing what is opened and closed. All other functionality and styling is up to you.

By default, modals are opened using a “last in first out” stack (this can be customized). The top modal is the only one that is visible, while the others below remain mounted but hidden determined by an isOpen prop.

MyModal.svelte
<script>
const { isOpen } = $props()
</script>
<!-- in most cases you can do this -->
{#if isOpen}
<div>
<!-- ... -->
</div>
{/if}
<!--
but if you prefer you can visually hide your modal instead,
preserving state of any children
-->
<div class:hidden={!isOpen} aria-hidden={!isOpen}>
<form>
<!-- ... -->
</form>
</div>
<style>
.hidden {
display: none;
}
</style>

Opening Modals

To open a modal, import modals and call modals.open(YourModalComponent, props).

<script>
import { modals } from 'svelte-modals'
import ConfirmModal from '$lib/components/ConfirmModal.svelte'
import AlertModal from '$lib/components/AlertModal.svelte'
async function show() {
const result = await modals.open(ConfirmModal, {
message: 'Are you sure?'
})
if (result === 'confirm') {
modals.open(AlertModal, { message: 'You confirmed' })
} else {
modals.open(AlertModal, { message: 'You cancelled' })
}
}
</script>
<button onclick={show}>Open</button>

Stacking Modals

You can call modals.open any time to open a new modal. This will add the new modal to the top of the stack, keeping the previous modals mounted but updating their isOpen prop to false.

<script>
import { modals} from 'svelte-modals'
import ConfirmModal from '$lib/components/ConfirmModal.svelte'
function openInfiniteModal() {
modals.open(ConfirmModal, {
message: 'Open another modal? This is modal #' + (modals.stack.length + 1),
onconfirm: () => openInfiniteModal()
})
}
</script>
<button onclick={openInfiniteModal}>Open</button>

Closing Modals

From anywhere in your app you can call modals.close() to close the top modal, modals.close(amount) to close the last given number of modals, or modals.closeAll() to close all modals.

<script>
import { modals } from 'svelte-modals'
// close the current modal
modals.close()
// close the last 2 modals
modals.close(2)
// close all modals
modals.closeAll()
</script>

The close() Prop

Modals receive a close prop which will close the modal and resolve their corresponding modals.open with the given value.

ConfirmModal.svelte
<script>
const { isOpen, close } = $props()
</script>
{#if isOpen}
<div>
<!-- ... -->
<button onclick={() => close('cancel')}>Cancel</button>
<button onclick={() => close('confirm')}>Confirm</button>
</div>
{/if}
<script>
import { modals } from 'svelte-modals'
import ConfirmModal from '$lib/components/ConfirmModal.svelte'
import AlertModal from '$lib/components/AlertModal.svelte'
async function show() {
const result = await modals.open(ConfirmModal, { message: 'Are you sure?' })
modals.open(AlertModal, { message: 'You chose: ' + result })
}
</script>
<button onclick={show}>Open</button>

If you are using Typescript, you can define the type of the value by using the ModalProps interface.

ConfirmModal.svelte
<script lang="ts">
import type { ModalProps } from 'svelte-modals'
type Result = 'cancel' | 'confirm'
const { isOpen, close }: ModalProps<Result> = $props()
</script>
{#if isOpen}
<div>
<!-- ... -->
<button onclick={() => close('cancel')}>Cancel</button>
<button onclick={() => close('confirm')}>Confirm</button>
</div>
{/if}
const result = await modals.open(ConfirmModal, { message: 'Are you sure?' })
result // 'cancel' | 'confirm'

Transitions

Transitions can be added to your modal components just like any other Svelte component. However they must be global transitions because they are mounted and unmounted by the Modals component.

MyModal.svelte
<script>
import { fade } from 'svelte/transition'
const { isOpen, close, title, message } = $props()
</script>
{#if isOpen}
<div
role="dialog"
class="modal-container"
transition:fade|global
>
<div class="modal-content">
<h2>{title}</h2>
<p>{message}</p>
<div class="modal-actions">
<button onclick={() => close()}>OK</button>
</div>
</div>
</div>
{/if}

Exit before enter

By default, transitions for both opening and closing modals will play at the same time. Depending on your transition this might be ok, but often it’s cleaner to play one at a time.

To opt-in to this behaviour, you can use the exitBeforeEnter action on the element that has the transitions.

MyModal.svelte
<script>
import { fade } from 'svelte/transition'
const { isOpen, exitBeforeEnter } = $props()
</script>
{#if isOpen}
<div
role="dialog"
use:exitBeforeEnter
transition:fade|global
>
<!-- ... -->
</div>
{/if}

Lazy Loading

Modal components can be lazy loaded with dynamic imports

import { modals } from 'svelte-modals'
modals.open(() => import('./AlertModal.svelte'), {
title: 'Lazy Modal',
message: 'This modal was loaded lazily'
})

While the component is being imported, the <Modals /> component will render backdrop and loading snippets.

Snippets

Snippets can be passed in as props the same way you would with a regular Svelte 5 component.

SnippetModal.svelte
<script>
const { isOpen, close, content } = $props()
</script>
{#if isOpen}
<div>
<!-- ... -->
{@render content()}
</div>
{/if}
<script>
import { modals } from 'svelte-modals'
import SnippetModal from '$lib/components/SnippetModal.svelte'
function onclick() {
modals.open(SnippetModal, {
content
})
}
</script>
{#snippet content()}
<div>Snippet content</div>
{/snippet}
<button onclick={onclick}>Open Modal</button>

You can also use Svelte’s createRawSnippet if you wish

<script>
import { modals } from 'svelte-modals'
import SnippetModal from '$lib/components/SnippetModal.svelte'
import { createRawSnippet } from 'svelte'
function onclick() {
modals.open(SnippetModal, {
content: createRawSnippet(() => ({
render: () => `<div>Snippet content</div>`
}))
})
}
</script>
<button onclick={onclick}>Open Modal</button>

Props

Modal components receive the following props:

interface ModalProps<ReturnValue = any> extends Record<string, any> {
// whether the modal is currently open
isOpen: boolean
// the unique id of the modal
id: string
// the index of the modal in the stack
index: number
// closes the modal with an optional return value
close: (value?: ReturnValue) => void
// for transitions, see Transitions section
onintrostart: () => void
onoutroend: () => void
}

If you’re using Typescript, you should use this interface to define the props of your modal components.

MyModal.svelte
<script lang="ts">
import type { ModalProps } from 'svelte-modals'
// optional
type CloseValue = 'cancel' | 'confirm'
interface MyModalProps extends ModalProps<CloseValue> {
title: string
}
const { isOpen, title, close }: MyModalProps = $props()
function handleConfirm() {
// uses CloseValue
close('confirm')
}
</script>