A simple, small, and content-agnostic modal for Svelte.
Live demo: https://svelte.dev/repl/136d296195b543c4a4a6de127ff9e06e?version=3.35.0
Works with: Svelte >=v3.35.0
npm install --save svelte-simple-modal
Make sure that the main application's version of svelte
is used for bundling by setting rollup-plugin-node-resolve
's dedupe
option as follows:
import resolve from 'rollup-plugin-node-resolve';
export default {
plugins: [
resolve({
dedupe: ['svelte', 'svelte/transition', 'svelte/internal'], // important!
}),
],
};
Make sure you install svelte-simple-modal as a dev-dependency.
npm install -D svelte-simple-modal
There are three ways to use the modal; with functions retrieved from context, setting modal content in a store or by using slots.
Import the Modal
component into your main app component (e.g., App.svelte
).
The modal expose two context functions:
<!-- App.svelte -->
<script>
import Content from './Content.svelte';
import Modal from 'svelte-simple-modal';
</script>
<Modal><Content /></Modal>
<!-- Content.svelte -->
<script>
import { getContext } from 'svelte';
import Popup from './Popup.svelte';
const { open } = getContext('simple-modal');
const showSurprise = () => open(Popup, { message: "It's a modal!" });
</script>
<p><button on:click={showSurprise}>Show me a surprise!</button></p>
<!-- Popup.svelte -->
<script>
export let message = 'Hi';
</script>
<p>π {message} πΎ</p>
Demo: https://svelte.dev/repl/52e0ade6d42546d8892faf8528c70d30
Alternatively, you can use a Svelte store to show/hide a component as a modal.
<!-- App.svelte -->
<script>
import { writable } from 'svelte/store';
import Modal, { bind } from 'svelte-simple-modal';
import Popup from './Popup.svelte';
const modal = writable(null);
const showModal = () => modal.set(bind(Popup, { message: "It's a modal!" }));
</script>
<Modal show={$modal}>
<button on:click={showModal}>Show modal</button>
</Modal>
Demo: https://svelte.dev/repl/aec0c7d9f5084e7daa64f6d0c8ef0209
The <Popup />
component is the same as in the example above.
To hide the modal programmatically, simply unset the store. E.g., modal.set(null)
.
You can also use slots for both modal content and close buttons. Content not marked with a slot will be placed in the default slot and shown where the modal is placed.
<!-- App.svelte -->
<script>
import Modal from 'svelte-simple-modal';
import Popup from './Popup.svelte';
let show = false;
</script>
<Modal bind:show>
<button on:click={() => (show = true)}>Show a popup!</button>
<Popup slot="modalContent" message="It's a modal!" />
</Modal>
Demo: https://svelte.dev/repl/2e0d504064934fef8fdad77dd160db70
The <Popup />
component is the same as in the examples above.
To hide the modal programmatically, simply set the show variable to a falsy value.
The modal comes pre-styled for convenience but you can easily extent or replace the styling using either custom CSS classes or explicit CSS styles.
Custom CSS classes can be applied via the classBg
, classWindow
, classWindowWrap
, classContent
, and classCloseButton
properties. For instance, you could customize the styling with TailwindCSS as follows:
<Modal
show={$modal}
unstyled={true}
classBg="fixed top-0 left-0 w-screen h-screen flex flex-col justify-center bg-orange-100/[.9]"
classWindowWrap="relative m-2 max-h-full"
classWindow="relative w-40 max-w-full max-h-full my-2 mx-auto text-orange-200 rounded shadow-md bg-indigo-900"
classContent="relative p-2 overflow-auto"
closeButton={false}
>
<button on:click={showModal}>Show modal</button>
</Modal>
Demo: https://svelte.dev/repl/f2a988ddbd5644f18d7cd4a4a8277993
Note: to take full control over the modal styles with CSS classes you have to reset existing styles via
unstyled={true}
as internal CSS classes are always applied last due to Svelte's class scoping.
Alternatively, you can also apply CSS styles directly via the styleBg
, styleWindow
, styleWindowWrap
, styleContent
, and styleCloseButton
properties. For instance:
<Modal
show={$modal}
styleBg={{ backgroundColor: 'rgba(255, 255, 255, 0.85)' }}
styleWindow={{ boxShadow: '0 2px 5px 0 rgba(0, 0, 0, 0.15)' }}
>
<button on:click={showModal}>Show modal</button>
</Modal>
Demo: https://svelte.dev/repl/50df1c694b3243c1bd524b27f86eec51
With SvelteKit you can enable SSR using the browser
environmental variable as follows:
<script>
import Modal from 'svelte-simple-modal';
import { browser } from '$app/env';
</script>
{#if browser}
<Modal>
<Content />
</Modal>
{/if}
Alternatively, you can enable SSR by dynamically importing svelte-simple-modal in onMount()
as follows:
import { onMount } from 'svelte';
onMount(async () => {
const svelteSimpleModal = await import('svelte-simple-modal');
Modal = svelteSimpleModal.default;
});
The library applies the following WAI-ARIA guidelines for modal dialogs automtically:
aria-modal="true"
androle="dialog"
are applied automatically- The tab focus is trapped in the modal
- The modal is closed on pressing the
esc
key
To further improve the accessibility you'll have to either provide a label via
ariaLabel
or reference a
title element via ariaLabelledBy
.
The ariaLabel
is automatically omitted when ariaLabelledBy
is specified.
The <Modal />
component accepts the following optional properties:
Property | Type | Default | Description |
---|---|---|---|
show | Component | null | null |
A Svelte component to show as the modal. See Store API for details. |
ariaLabel | string | null | null |
Accessibility label of the modal. See W3C WAI-ARIA for details. |
ariaLabelledBy | string | null | null |
Element ID holding the accessibility label of the modal. See W3C WAI-ARIA for details. |
closeButton | Component | boolean | true |
If true a button for closing the modal is rendered. You can also pass in a custom Svelte component to have full control over the styling. |
closeOnEsc | boolean | true |
If true the modal will close when pressing the escape key. |
closeOnOuterClick | boolean | true |
If true the modal will close when clicking outside the modal window. |
transitionBg | function | svelte.fade |
Transition function for the background. |
transitionBgProps | BlurParams | {} |
Properties of the transition function for the background. |
transitionWindow | function | svelte.fade |
Transition function for the window. |
transitionWindowProps | BlurParams | {} |
Properties of the transition function for the window. |
classBg | string | null | null |
Class name for the background element. |
classWindowWrap | string | null | null |
Class name for the modal window wrapper element. |
classWindow | string | null | null |
Class name for the modal window element. |
classContent | string | null | null |
Class name for the modal content element. |
classCloseButton | string | null | null |
Class name for the built-in close button. |
styleBg | Record<string, string | number> | {} |
Style properties of the background. |
styleWindowWrap | Record<string, string | number> | {} |
Style properties of the modal window wrapper element. |
styleWindow | Record<string, string | number> | {} |
Style properties of the modal window. |
styleContent | Record<string, string | number> | {} |
Style properties of the modal content. |
styleCloseButton | Record<string, string | number> | {} |
Style properties of the built-in close button. |
unstyled | boolean | false |
When true , the default styles are not applied to the modal elements. |
disableFocusTrap | boolean | false |
If true elements outside the modal can be in focus. This can be useful in certain edge cases. |
isTabbable | (node: Element): boolean | internal function | A function to determine if an HTML element is tabbable. |
key | string | "simple-modal" |
The context key that is used to expose open() and close() . Adjust to avoid clashes with other contexts. |
setContext | function | svelte.setContent |
You can normally ingore this property when you have configured Rollup properly. If you want to bundle simple-modal with its own version of Svelte you have to pass setContext() from your main app to simple-modal using this parameter. |
callbacks | {onOpen: (event: CustomEvent) => void, onOpened: (event: CustomEvent) => void, onClose: (event: CustomEvent) => void, onClosed: (event: CustomEvent) => void } | null | null | An object with callbacks to be called on opening and closing transitions. Can be used in addition to the callbacks passed to bind, or alone. |
The <Modal />
component dispatches the following events:
open
: dispatched when the modal window starts to open.opened
: dispatched when the modal window opened.close
: dispatched when the modal window starts to close.closed
: dispatched when the modal window closed.
Alternatively, you can listen to those events via callbacks passed to open()
and close()
.
Svelte Simple Modal uses Svelte's context API to expose the open()
and close()
methods. You can get these methods as follows:
const { open, close } = getContext('simple-modal');
# open(Component, props = {}, options = {}, callbacks = {})
Opens the modal with <Component {props}>
rendered as the content. options
can be used to adjust the modal behavior once for the modal that is about to be opened. The options
allows to customize all parameters except key
and setContext
:
{
closeButton: false,
closeOnEsc: false,
closeOnOuterClick: false,
transitionBg: fade,
transitionBgProps: {
duration: 5000
},
transitionWindow: fly,
transitionWindowProps: {
y: 100,
duration: 250
},
styleBg: { backgroundImage: 'http://example.com/my-background.jpg' },
styleWindow: { fontSize: '20em' },
styleContent: { color: 'yellow' },
styleCloseButton: { width: '3rem', height: '3rem' },
disableFocusTrap: true
}
# close(callbacks = {})
Closes the modal. Similar to open()
, this method supports adding callbacks for the closing transition:
{
onClose: () => { /* modal window starts to close */ },
onClosed: () => { /* modal window closed */ },
}
Callbacks are triggered at the beginning and end of the opening and closing transition. The following callbacks are supported:
{
onOpen: () => { /* modal window starts to open */ },
onOpened: () => { /* modal window opened */ },
onClose: () => { /* modal window starts to close */ },
onClosed: () => { /* modal window closed */ },
}
You can also use Svelte stores to open a modal using the <Modal />
's show
property as follows:
<!-- App.svelte -->
<script>
import { writable } from 'svelte/store';
import Modal from 'svelte-simple-modal';
import Popup from './Popup.svelte';
const modal = writable(null);
const showModal = () => modal.set(Popup);
</script>
<Modal show={$modal}>
<button on:click={showModal}>Show modal</button>
</Modal>
<!-- Popup.svelte -->
<p>π Hi πΎ</p>
Demo: https://svelte.dev/repl/6f55b643195646408e780ceeb779ac2a
An added benefit of using stores is that the component opening the modal does not have to be a parent of <Modal />
. For instance, in the example above, App.svelte
is toggling Popup.svelte
as a modal even though App.svelte
is not a child of <Modal />
.
You can use slots to place the modal content and close button. Content which are not marked with a slot will be placed in the default slot, and shown where the modal is placed. To summarize;
Slot name | Example | Placement |
---|---|---|
modalContent | slot="modalContent" |
Content of the modal |
closeButton | slot="closeButton" |
Close button of the modal |
modalContent | slot="example" |
Not shown |
no slot provided | empty | Where the modal component is placed in the DOM, without wrapping |
<!-- App.svelte -->
<script>
import Modal from 'svelte-simple-modal';
import Popup from './Popup.svelte';
let show = false;
</script>
<Modal bind:show>
<button on:click={() => (show = true)}>Show a popup!</button>
<Popup slot="modalContent" message="It's a modal!" />
</Modal>
<!-- Popup.svelte -->
<p>π Hi πΎ</p>
Demo: https://svelte.dev/repl/2e0d504064934fef8fdad77dd160db70
The benefit of using slots is that you can still send in props as normal, meaning that the component props will be reactive as before. It's also easy to hide the modal programatically when some condition are met, e.g. the list triggering the modal is suddenly empty, as you only have to unset the show variable.
Sometimes you want to pass properties to the component shown as a modal. To accomplish this, you can use our bind()
helper function as follows:
<script>
import { bind } from 'svelte-simple-modal';
import Popup from './Popup.svelte';
import { modal } from './App.svelte';
modal.set(bind(Popup, { name: 'custom name' }));
</script>
If you've worked with React/JSX then think of const c = bind(Component, props)
as the equivalent of const c = <Component ...props />
.
This feature requires Svelte >=v3.19!
Unfortunately, it's not possible to adjust all styles of the built-in close button via the styleCloseButton
option. If you need full control you can implement your own Svelte component and use that as the close button. To do so pass your component via the closeButton
option to <Modal />
or open()
.
For example, your close button might look as follows:
<!-- CloseButton.svelte -->
<script>
// This property is used by Modal.svelte to pass down the close function
export let onClose;
</script>
<button on:click={onClose}>Custom Close Button</button>
<style>
/* Customize to your liking */
button {
position: absolute;
top: -3rem;
right: 0;
}
</style>
Now you can set it as the default close button by passing it to <Modal />
as follows:
<!-- App.svelte -->
<script>
import Content from './Content.svelte';
import CloseButton from './CloseButton.svelte';
import Modal from 'svelte-simple-modal';
</script>
<Modal closeButton={CloseButton}>
<Content />
</Modal>
Or you can pass CloseButton
to open()
as shown below:
<!-- Content.svelte -->
<script>
import { getContext } from 'svelte';
import Surprise from './Surprise.svelte';
import CloseButton from './CloseButton.svelte';
const { open } = getContext('simple-modal');
const showSurprise = () => {
open(Surprise, { message: "It's a modal!" }, { closeButton: CloseButton });
};
</script>
<p><button on:click={showSurprise}>Show me a surprise!</button></p>