Physics-based toast notifications for Svelte 5.
npm install sileo-svelte
<!-- src/routes/+layout.svelte -->
<script>
import { Toaster } from 'sileo-svelte';
import 'sileo-svelte/styles.css';
let { children } = $props();
</script>
{@render children()}
<Toaster position="top-right" />
<script>
import { sileo } from 'sileo-svelte';
</script>
<button onclick={() => sileo.success('Saved', 'Your changes have been saved.')}>Save</button>
<Toaster /> PropsOnly one <Toaster /> is needed in your app.
| Prop | Type | Default | Description |
|---|---|---|---|
position |
SileoPosition |
'top-right' |
Default viewport for new toasts. Per-toast position still overrides this. |
offset |
number | string | SileoOffsetConfig |
undefined |
Offset from screen edges. Number values are treated as px. |
options |
Partial<SileoOptions> |
undefined |
Global defaults merged into every toast call. Per-toast fields win. classes and styles are merged per-key. |
children |
Snippet |
undefined |
Optional content rendered before toast viewports. |
SileoOffsetConfig:
type SileoOffsetConfig = Partial<{
top: number | string;
right: number | string;
bottom: number | string;
left: number | string;
}>;
SileoPosition:
'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
sileo APIAll creation methods return the toast id: string.
show(input: SileoInput, description?: SileoOptions['description']): string
success(input: SileoInput, description?: SileoOptions['description']): string
error(input: SileoInput, description?: SileoOptions['description']): string
warning(input: SileoInput, description?: SileoOptions['description']): string
info(input: SileoInput, description?: SileoOptions['description']): string
action(input: SileoInput, description?: SileoOptions['description']): string
loading(input: SileoInput, description?: SileoOptions['description']): string
SileoInput:
type SileoInput = SileoOptions | string;
Examples:
sileo.success({ title: 'Saved' });
sileo.success('Saved');
sileo.success('Saved', 'Your changes have been saved.');
// loading defaults to duration: null unless explicitly provided
const id = sileo.loading('Uploading...');
sileo.update(id, { state: 'success', title: 'Uploaded' });
Use sileo.with(defaults) to avoid repeating common options.
const billingToasts = sileo.with({
position: 'bottom-right',
duration: 4000,
fill: '#121212'
});
billingToasts.success('Invoice paid');
billingToasts.error('Payment failed', 'Please retry with another card.');
Defaults are merged recursively for classes and styles, and shallow-merged for other fields.
promise<T>(
promise: Promise<T> | (() => Promise<T>),
opts: SileoPromiseOptions<T>
): Promise<T>
sileo.promise(() => fetch('/api/upload', { method: 'POST' }), {
loading: { title: 'Uploading' },
success: () => ({ title: 'Done', description: 'File uploaded successfully!' }),
error: (err) => ({
title: 'Failed',
description: err instanceof Error ? err.message : 'Upload failed.'
})
});
SileoPromiseOptions<T>:
| Option | Type | Required | Description |
|---|---|---|---|
id |
string |
No | Existing toast id to morph instead of creating a new one. |
loading |
Pick<SileoOptions, 'title' | 'icon'> |
Yes | Pending state content. |
success |
SileoOptions | ((data: T) => SileoOptions) |
Yes | Success state content. |
error |
SileoOptions | ((err: unknown) => SileoOptions) |
Yes | Error state content. |
action |
SileoOptions | ((data: T) => SileoOptions) |
No | If provided, replaces success state. |
position |
SileoPosition |
No | Position override for the promise toast. |
update(id: string, opts: SileoOptions & { state?: SileoState }): void
dismiss(id: string): void
close(id: string): void
clear(position?: SileoPosition): void
update: morphs content/state in place.dismiss: immediate exit animation.close: collapse first, then exit.clear: remove all toasts (or a specific viewport).SileoOptions| Field | Type | Default | Description |
|---|---|---|---|
title |
string |
State name | Header title. |
description |
string | Snippet |
undefined |
Expanded body content. |
position |
SileoPosition |
Toaster position |
Per-toast viewport override. |
duration |
number | null |
6000 |
Auto-dismiss timeout in ms. null keeps it persistent. |
icon |
Snippet | null |
State icon | Custom icon snippet; null hides icon. |
classes |
SileoClasses |
undefined |
Per-part class overrides. |
styles |
SileoStyles |
undefined |
Per-toast color variable overrides (Tailwind-free). |
fill |
string |
'#1c1c1e' |
Toast background color. |
roundness |
number |
18 |
Corner radius. |
autopilot |
boolean | { expand?: number; collapse?: number } |
true |
Automatic expand/collapse behavior. |
button |
SileoButton |
undefined |
Action button shown in expanded body. |
SileoClasses:
interface SileoClasses {
title?: string;
description?: string;
badge?: string;
button?: string;
}
SileoStyles:
interface SileoStyles {
titleColor?: string;
descriptionColor?: string;
badgeColor?: string;
badgeBackground?: string;
buttonColor?: string;
buttonBackground?: string;
buttonHoverBackground?: string;
}
SileoButton:
interface SileoButton {
title: string;
onClick: (id: string) => void;
}
SileoState:
'success' | 'loading' | 'error' | 'warning' | 'info' | 'action';
sileo.success({ title: 'Dark', description: 'Custom background.', fill: '#1a1a2e' });
sileo.info({ title: 'Square', description: 'Reduced roundness.', roundness: 4 });
sileo.warning({ title: 'Manual', description: 'Hover to read.', autopilot: false });
classes and styles// Class-based styling
sileo.action({
title: 'Class styling',
description: 'Uses classes.title/classes.description/classes.button',
classes: {
title: 'text-foreground',
description: 'text-foreground',
button: 'text-primary-foreground hover:bg-primary/90'
},
button: { title: 'Close', onClick: (id) => sileo.close(id) }
});
// Variable-based styling (recommended if utility extraction is inconsistent)
sileo.action({
title: 'Variable styling',
description: 'Uses styles.*',
styles: {
titleColor: 'var(--foreground)',
descriptionColor: 'var(--foreground)',
buttonColor: 'var(--primary-foreground)',
buttonBackground: 'var(--primary)',
buttonHoverBackground: 'color-mix(in oklch, var(--primary) 85%, black)'
},
button: { title: 'Close', onClick: (id) => sileo.close(id) }
});
Tailwind note:
options.classes.button), Tailwind may not always detect them.@source inline("text-foreground text-primary-foreground hover:bg-primary/90").const id = sileo.loading('Uploading file...');
sileo.update(id, { title: 'Uploading', description: '50%' });
sileo.update(id, { state: 'success', title: 'Done', description: '100%', duration: 4000 });
description and icon)SileoOptions supports snippets in two places:
description: rich body content in the expanded areaicon: custom header icon badge<script>
import { sileo } from 'sileo-svelte';
</script>
{#snippet customDescription()}
<div>
<strong>Release ready.</strong>
<p>Click deploy to roll out v2.4.0.</p>
</div>
{/snippet}
{#snippet rocketIcon()}
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M4.5 16.5c-1.5 1.26-2 3.75-2 3.75s2.49-.5 3.75-2l3-3-1.75-1.75z" />
<path d="M14 10l-4 4" />
<path d="M16 4l4 4" />
<path d="M21.17 2.83a2.83 2.83 0 0 1 0 4L11 17l-4-4L17.17 2.83a2.83 2.83 0 0 1 4 0z" />
</svg>
{/snippet}
<button
onclick={() =>
sileo.action({
title: 'Deploy',
description: customDescription,
icon: rocketIcon,
button: {
title: 'Run',
onClick: (id) => sileo.close(id)
}
})}>Show Rich Toast</button
>
Notes:
description and icon both accept either plain values (string / null) or snippets.icon: null to hide the icon entirely.sileo.update(...) payloads, so you can morph to/from rich content.const id = sileo.loading('Deploying v2.4.0...');
await delay(1000);
sileo.update(id, {
state: 'info',
title: 'Uploading build',
description: 'Artifacts uploaded to edge cache.'
});
await delay(1000);
sileo.update(id, {
state: 'warning',
title: 'Running health checks',
description: 'Smoke tests on 6 regions...'
});
// Final stage: morph same toast through promise lifecycle
sileo.promise(
async () => {
await delay(900);
return await releaseToProduction();
},
{
id,
loading: { title: 'Rolling out' },
success: () => ({
state: 'action',
title: 'Deployment complete',
description: 'Traffic switched to v2.4.0.',
button: {
title: 'View logs',
onClick: (toastId) => {
openDeploymentLogs();
sileo.close(toastId);
}
}
}),
error: (err) => ({
title: 'Rollback triggered',
description: err instanceof Error ? err.message : 'Unknown deploy error',
duration: null
})
}
);
// Action-first flow that morphs into promise states
sileo.action({
title: 'Update available',
description: 'Version 2.0 is ready to install.',
button: {
title: 'Install now',
onClick: (id) => {
sileo.promise(() => installUpdate(), {
id,
loading: { title: 'Installing' },
success: () => ({
state: 'action',
title: 'Installed successfully',
description: 'Restart app to apply the update.',
button: {
title: 'Restart',
onClick: (toastId) => sileo.close(toastId)
}
}),
error: (err) => ({
state: 'action',
title: 'Install failed',
description: err instanceof Error ? err.message : 'Unknown install error',
duration: null,
button: {
title: 'Retry',
onClick: (toastId) => {
sileo.promise(() => installUpdate(), {
id: toastId,
loading: { title: 'Retrying install' },
success: { title: 'Retry succeeded', description: 'Update installed.' },
error: { title: 'Still failing', description: 'Please check logs.', duration: null }
});
}
}
})
});
}
}
});
<Toaster
position="bottom-right"
options={{
duration: 4000,
fill: '#0a0a0a',
roundness: 12
}}
offset={{ top: 60, right: 16 }}
/>
import {
Toaster,
sileo,
type SileoApi,
type SileoScopedApi,
type SileoInput,
type SileoOptions,
type SileoPosition,
type SileoState,
type SileoClasses,
type SileoStyles,
type SileoButton,
type SileoPromiseOptions
} from 'sileo-svelte';
Import CSS separately:
import 'sileo-svelte/styles.css';
Override these variables globally:
:root {
--sileo-duration: 600ms;
--sileo-height: 40px;
--sileo-width: 350px;
--sileo-state-success: oklch(0.723 0.219 142.136);
--sileo-state-loading: oklch(0.75 0 0);
--sileo-state-error: oklch(0.637 0.237 25.331);
--sileo-state-warning: oklch(0.795 0.184 86.047);
--sileo-state-info: oklch(0.685 0.169 237.323);
--sileo-state-action: oklch(0.623 0.214 259.815);
}
Per-toast variables set via options.styles map to these CSS vars on each toast root:
--sileo-title-color--sileo-description-color--sileo-badge-color--sileo-badge-bg--sileo-button-color--sileo-button-bg--sileo-button-bg-hoverMIT