Keyboard
No component-specific keymap. Inherits standard browser focus / activation behaviour.
Pure state machines (FSMs). No DOM. Run in server, Vitest, Worker. Reach for these when you only want the behavior.
Live-region notification queue.
pnpm add @kumiki/machinesimport { createToastMachine } from '@kumiki/machines/toast';
const m = createToastMachine({ max: 3 });
m.send({ type: 'ADD', toast: { id: 'a', title: 'Saved' } });
console.log(m.context.toasts.length); // 1
m.send({ type: 'CLEAR' });
console.log(m.context.toasts.length); // 0Svelte 5 {@attach} factories. Glue ARIA / keyboard / focus onto any DOM node you choose. **Best when you want full control over markup and styling.**
pnpm add @kumiki/headless<script lang="ts">
import { createToast } from '@kumiki/headless/toast';
const t = createToast({ defaultDuration: 4000 });
function notify() {
t.add({ title: 'Saved', description: 'Your changes were saved.' });
}
</script>
<button onclick={notify}>Notify</button>
<ol {@attach t.viewport}>
{#each t.toasts as toast (toast.id)}
<li {@attach t.item(toast.id)}>
<strong>{toast.title}</strong>
<p>{toast.description}</p>
<button {@attach t.closeButton(toast.id)}>×</button>
</li>
{/each}
</ol>Compound components (<Root> / <Trigger> / …). Markup is fixed; styling is not. Same trade-off as a typical headless UI library.
pnpm add @kumiki/components/toast<script lang="ts">
import { Toaster, Viewport, Item, Title, Description, Close } from '@kumiki/components/toast';
</script>
<Toaster defaultDuration={4000}>
{#snippet children({ toasts, controller })}
<Viewport>
{#each toasts as toast (toast.id)}
<Item {toast}>
<Title>{toast.title}</Title>
{#if toast.description}<Description>{toast.description}</Description>{/if}
<Close>×</Close>
</Item>
{/each}
</Viewport>
<button onclick={() => controller.add({ title: 'Saved' })}>Notify</button>
{/snippet}
</Toaster>Styled, copy-paste presets (preview). Run pnpm kumiki add to drop the source into your project, then edit freely.
Live preview…
pnpm add @kumiki/atelier<script lang="ts">
import { Tailwind as Toast } from '@kumiki/atelier/toast';
let counter = 0;
</script>
<Toast.Toaster defaultDuration={4000}>
{#snippet children({ toasts, controller })}
<button onclick={() =>
controller.add({
id: `s-${++counter}`,
title: 'Saved',
description: 'Your changes were saved.',
type: 'success',
})}
>Save</button>
<Toast.Viewport>
{#each toasts as toast (toast.id)}
<Toast.Item {toast}>
<Toast.Title>{toast.title}</Toast.Title>
{#if toast.description}<Toast.Description>{toast.description}</Toast.Description>{/if}
<Toast.Close />
</Toast.Item>
{/each}
</Toast.Viewport>
{/snippet}
</Toast.Toaster>/ accessibility
axe-core — run on every PR (LTR + RTL × every documented state).
No component-specific keymap. Inherits standard browser focus / activation behaviour.