Toast you say? 🍞
A common UI design pattern is to use "toasts" or small UI notifications that alert the user of something happening in realtime (e.g. a form submission error, a new message or friend request, etc).
In this article, we will be building a simple toast system in Svelte, kinda like this:
Impatient? See the REPL here
Create a Svelte store for out toast notifications
Let's start out by creating a simple Svelte store for our toast system. The store will just contain an array that we will update when a new toast is created or "dismissed":
import { writable } from 'svelte/store'
export const toasts = writable([])
export const dismissToast = (id) => {
toasts.update((all) => all.filter((t) => t.id !== id))
}
export const addToast = (toast) => {
// Create a unique ID so we can easily find/remove it
// if it is dismissible/has a timeout.
const id = Math.floor(Math.random() * 10000)
// Setup some sensible defaults for a toast.
const defaults = {
id,
type: 'info',
dismissible: true,
timeout: 3000,
}
// Push the toast to the top of the list of toasts
const t = { ...defaults, ...toast }
toasts.update((all) => [t, ...all])
// If toast is dismissible, dismiss it after "timeout" amount of time.
if (t.timeout) setTimeout(() => dismissToast(id), t.timeout)
}
Overall this should be pretty simple, we have a two methods, one for adding a toast and the other for removing. If the toast has a timeout
field, we set a timeout to remove the toast. We set some default values for all toasts and we give a toast an id
to make it easier to add/remove and for Svelte's {#each}
tag to index it better.
Create the toasts parent component
<script lang="ts">
import Toast from './Toast.svelte'
import { dismissToast, toasts } from './store'
</script>
{#if $toasts}
<section>
{#each $toasts as toast (toast.id)}
<Toast
type={toast.type}
dismissible={toast.dismissible}
on:dismiss={() => dismissToast(toast.id)}>{toast.message}</Toast>
{/each}
</section>
{/if}
<style lang="postcss">
section {
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
display: flex;
margin-top: 1rem;
justify-content: center;
flex-direction: column;
z-index: 1000;
}
</style>
Create the toast component
Next, we're going to create a Toast.svelte
component with different states: success, error, and info, like so:
<script>
import { createEventDispatcher } from 'svelte'
import { fade } from 'svelte/transition'
import SuccessIcon from './SuccessIcon.svelte'
import ErrorIcon from './ErrorIcon.svelte'
import InfoIcon from './InfoIcon.svelte'
import CloseIcon from './CloseIcon.svelte'
const dispatch = createEventDispatcher()
export let type = 'error'
export let dismissible = true
</script>
<article class={type} role="alert" transition:fade>
{#if type === 'success'}
<SuccessIcon width="1.1em" />
{:else if type === 'error'}
<ErrorIcon width="1.1em" />
{:else}
<InfoIcon width="1.1em" />
{/if}
<div class="text">
<slot />
</div>
{#if dismissible}
<button class="close" on:click={() => dispatch('dismiss')}>
<CloseIcon width="0.8em" />
</button>
{/if}
</article>
<style lang="postcss">
article {
color: white;
padding: 0.75rem 1.5rem;
border-radius: 0.2rem;
display: flex;
align-items: center;
margin: 0 auto 0.5rem auto;
width: 20rem;
}
.error {
background: IndianRed;
}
.success {
background: MediumSeaGreen;
}
.info {
background: SkyBlue;
}
.text {
margin-left: 1rem;
}
button {
color: white;
background: transparent;
border: 0 none;
padding: 0;
margin: 0 0 0 auto;
line-height: 1;
font-size: 1rem;
}
</style>
Hopefully this component is pretty straight forward; it's just some styling for the toast, some conditions for if it is "dismissible" and come icon components (which are just SVGs).
Creating toast notifications
You can now create toast notifications anywhere in your Svelte app (in your JS files or your .svelte
files):
import { addToast } from "./store";
addToast({
message: "Hello, World!",
type: "success",
dismissible: true,
timeout: 3000,
});
You can then use your <Toasts />
component somewhere in your layout component (e.g. App.svelte
or _layout.svelte
, etc).
Wrapping up 🌯
That's it folks, hopefully you learning something today!
See the full toast system in the Svelte REPL here.
Thanks for reading!
Thanks for reading! Consider giving this post a ❤️, 🦄 or 🔖 to bookmark it for later. 💕
Have other tips, ideas, feedback or corrections? Let me know in the comments! 🙋♂️
Don't forget to follow me on Dev.to (danawoodman), Twitter (@danawoodman) and/or Github (danawoodman)!
Photo by Joshua Aragon on Unsplash
Top comments (5)
Wow you get these out fast 😅
It's good to see
role="alert"
on the toast for accessibility!It would also be good to make sure the close button has an accessible name and focus styles for keyboard and assistive tech users. Otherwise the button will receive focus, but not indicate its purpose.
What about simply giving the button a title of "close?" That would be accessible for screen readers and assistive tech right?
If you want to do it using an attribute,
aria-label="Close"
would be preferable totitle
, since title is inconsistently announced by screen readers. However, visually hidden text (the technique above) is more likely to be translated.Got it, thank you.
Haha thanks Geoff! Will update the article, good feedback 💯