sileo-svelte
v0.0.5
Published
Physics-based gooey toast notifications for Svelte 5
Maintainers
Readme
sileo-svelte
Physics-based toast notifications for Svelte 5.
Installation
npm install sileo-svelteQuick Start
1. Mount the toaster once
<!-- 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" />2. Trigger toasts from anywhere
<script>
import { sileo } from 'sileo-svelte';
</script>
<button onclick={() => sileo.success('Saved', 'Your changes have been saved.')}>Save</button><Toaster /> Props
Only 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 API
All creation methods return the toast id: string.
Creation methods
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']): stringSileoInput:
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' });Scoped defaults
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 helper
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 and dismissal
update(id: string, opts: SileoOptions & { state?: SileoState }): void
dismiss(id: string): void
close(id: string): void
clear(position?: SileoPosition): voidupdate: 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';Practical Examples
Custom appearance
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 });Styling with 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:
- If utility classes are passed inside JS strings (like
options.classes.button), Tailwind may not always detect them. - For Tailwind v4, safelist them via
@source inline("text-foreground text-primary-foreground hover:bg-primary/90").
Reusing a toast id
const id = sileo.loading('Uploading file...');
sileo.update(id, { title: 'Uploading', description: '50%' });
sileo.update(id, { state: 'success', title: 'Done', description: '100%', duration: 4000 });Svelte snippets for rich content (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:
descriptionandiconboth accept either plain values (string/null) or snippets.- Set
icon: nullto hide the icon entirely. - Snippets are also valid in
sileo.update(...)payloads, so you can morph to/from rich content.
Advanced workflows (update + promise orchestration)
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 defaults and viewport offset
<Toaster
position="bottom-right"
options={{
duration: 4000,
fill: '#0a0a0a',
roundness: 12
}}
offset={{ top: 60, right: 16 }}
/>Exports
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';CSS Customization
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-hover
Credits
- Original library: hiaaryan/sileo
- Original demo: sileo.aaryan.design
License
MIT
