A lightweight, accessible toast notification system built for Svelte 5 using the Context API pattern. This interactive demo showcases dynamic position switching and all toast variants.
# Install dependencies
pnpm install
# Start development server
pnpm dev
src/lib/components/toast/
├── index.ts # Public exports
├── types.ts # TypeScript interfaces
├── toast-context.svelte.ts # Context and state management
├── ToastProvider.svelte # Provider component
├── ToastViewport.svelte # Renders toast collection
└── ToastItem.svelte # Individual toast component
This demo extends the base toast system to support dynamic position switching. Understanding the difference:
In the companion article, position is set once on ToastProvider and stored in context:
<!-- Position fixed for app lifetime -->
<ToastProvider position="bottom-right">
{@render children()}
<ToastViewport />
</ToastProvider>
The ToastViewport reads position from context via getToastPosition(). This is simpler and sufficient for most applications.
For runtime position changes, we:
position prop to ToastViewport that overrides contextToastViewport for reactive updates<!-- +layout.svelte -->
<script lang="ts">
let currentPosition = $state<ToastPosition>('top-right');
// Expose setter via context for child components
setContext('toast-position-control', {
get position() {
return currentPosition;
},
setPosition: (pos) => {
currentPosition = pos;
}
});
</script>
<ToastProvider defaultDuration={5000} maxToasts={5}>
{@render children()}
<!-- Position prop enables reactive updates -->
<ToastViewport position={currentPosition} />
</ToastProvider>
Why this works: ToastProvider uses untrack() which only captures initial values. The ToastViewport position prop is reactive via $derived(positionProp ?? contextPosition).
| Use Case | Where to set position |
|---|---|
| Static (never changes) | ToastProvider |
| Dynamic (user can change) | ToastViewport prop |
<script lang="ts">
import { getToastContext } from '$lib/components/toast';
const toast = getToastContext();
</script>
<button onclick={() => toast.success('Saved!')}> Save </button>
// Convenience methods
toast.info('Information message');
toast.success('Operation completed!');
toast.warning('Check your input', 10000); // custom duration
toast.error('Something went wrong');
// Full options API
const id = toast.show({
message: 'Custom toast',
type: 'info',
duration: 0 // 0 = no auto-dismiss
});
// Programmatic dismiss
toast.dismiss(id);
toast.dismissAll();
<ToastProvider
defaultDuration={5000} <!-- Auto-dismiss time in ms -->
maxToasts={5} <!-- Maximum visible toasts -->
position="bottom-right" <!-- Initial position (static mode) -->
>
| Method | Description |
|---|---|
toast.info(message, duration?) |
Show info toast |
toast.success(message, duration?) |
Show success toast |
toast.warning(message, duration?) |
Show warning toast |
toast.error(message, duration?) |
Show error toast |
toast.show(options) |
Show toast with full options |
toast.dismiss(id) |
Dismiss specific toast |
toast.dismissAll() |
Dismiss all toasts |
toast.toasts |
Read-only array of active toasts |
type ToastPosition =
| 'top-right'
| 'top-left'
| 'top-center'
| 'bottom-right'
| 'bottom-left'
| 'bottom-center';
toasts array is immutable to consumersThis is a demo project, but feel free to fork and customize for your needs!
MIT
Built with ❤️ using Svelte 5 and SvelteKit