Skip to content

Modal Component

Modals are opened using a “last in first out” stack. If multiple modals are opened, only the top one is shown and the others will be hidden (by providing an isActive prop to the modal). When a modal is closed, it unmounts and shows the next modal in the stack (if there is one).

Opening Modals

Modals are opened with modals.open(YourModalComponent, props).

<script>
import { modals } from 'svelte-modals'
import ConfirmModal from '../components/ConfirmModal.svelte'
import AlertModal from '../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>

Multiple Modals

If another modal is opened while a modal is already open, the current modal will be hidden (by receiving an isActive prop of false) and the new modal will be shown.

<script>
import { modals} from 'svelte-modals'
import ConfirmModal from '../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 a given number of modals, or modals.closeAll() to close all modals.

<script>
import { modals } from 'svelte-modals'
// close 1 modal
modals.close()
// close 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 the corresponding modals.open with the given value.

ConfirmModal.svelte
<script>
const { isActive, close } = $props()
</script>
{#if isActive}
<div>
<!-- ... -->
<button onclick={() => close('cancel')}>Cancel</button>
<button onclick={() => close('confirm')}>Confirm</button>
</div>
{/if}
<script>
import { modals } from 'svelte-modals'
import ConfirmModal from '../components/ConfirmModal.svelte'
import AlertModal from '../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 { isActive, close } = $props<ModalProps<Result>>()
</script>
{#if isActive}
<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'

Props

Modal components receive the following props:

interface ModalProps<ReturnValue = any> extends Record<string, any> {
// whether the modal is currently open
isActive: 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'
interface MyModalProps extends ModalProps {
title: string
}
const { isActive, title, close } = $props<MyModalProps>()
</script>
{#if isActive}
<div role="dialog">
<!-- ... -->
</div>
{/if}

Transitions

Transitions can be added to your modal components just like any other Svelte component. They must be global transitions because they are mounted and unmounted by the <ModalStack /> component.

<script>
import { fade } from 'svelte/transition'
const { isActive, close, title, message } = $props()
</script>
{#if isActive}
<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}

Transitions between Modals

By default, when opening one modal after another the transitions for both will play at the same time. Depending on your animation this might be ok, but often it’s cleaner to transition one at a time.

To control this behaviour, you can forward on the onintrostart and onoutroend props to your modal. This tells <ModalStack /> to wait for the transition to finish before opening the next modal.

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

See how they compare

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 <ModalStack /> component will render backdrop and loading snippets.