Klarhimmel UI

Toast

Stable

A short message that appears at the bottom, from anywhere in the app, and leaves by itself.

npx shadcn add @klarhimmel/toast @klarhimmel/button

When to use

  • Use for

    Confirming what already happened: saved, copied, removed with Undo.

  • Not for

    Errors or choices that need action. Use a Banner or a Dialog.

  • Pairs with

    Button, a list with Undo.

Preview

Each toast leaves on its own, and stays while you point at it or focus it.

toast.tsx
import { Theme } from '@/components/klarhimmel/theme/style';
import { Button } from '@/components/klarhimmel/ui/button';
import { Toaster, toast } from '@/components/klarhimmel/ui/toast';

export default function Example() {
  return (
    <Theme theme="brand">
      <Toaster />
      <Button onClick={() => toast('Changes saved')}>Save</Button>
      <Button
        variant="secondary"
        onClick={() => toast('Room removed', { action: { label: 'Undo', onClick: () => console.log('restore') } })}
      >
        Remove room
      </Button>
    </Theme>
  );
}

Install

Import

import { Toaster, toast, dismissToast, clearToasts } from '@/components/klarhimmel/ui/toast';
Lands in
components/klarhimmel/ui/toast.tsx, plus cn() and the theme item if they are missing. Import theme.css after Tailwind.
Render in
Render it inside <Theme>, once near the root of your app. Tokens and fonts are scoped to it, so outside it the component is unstyled.
Limit
Overlays render into the Theme root with position: fixed. A transform on an ancestor re-parents fixed (a dialog centres in the wrapper) and overflow: hidden clips menus. Render <Theme> at the app root, outside any transformed wrapper.

Examples

  • Tones

    Neutral and info are polite; danger is announced at once.

    import { Button } from '@/components/klarhimmel/ui/button';
    import { Toaster, toast } from '@/components/klarhimmel/ui/toast';
    
    export default function ToastTones() {
      return (
        <div className="flex min-h-20 flex-col items-start gap-3">
          <div className="flex flex-wrap gap-3">
            <Button variant="secondary" onClick={() => toast('Copied the booking link')}>Neutral</Button>
            <Button variant="secondary" onClick={() => toast('Prices updated', { tone: 'info', description: 'Three rooms changed.' })}>Info</Button>
            <Button variant="secondary" onClick={() => toast('Could not sync the calendar', { tone: 'danger', description: 'Try again in a minute.' })}>Danger</Button>
          </div>
          <p className="text-kh-small text-kh-muted">Neutral and info are polite; danger is announced at once.</p>
          <Toaster />
        </div>
      );
    }
  • Action

    • Seaside suite
    • Garden room
    import { useState } from 'react';
    import { Button } from '@/components/klarhimmel/ui/button';
    import { Toaster, toast } from '@/components/klarhimmel/ui/toast';
    
    export default function ToastAction() {
      const [rooms, setRooms] = useState(['Seaside suite', 'Garden room']);
      const remove = (room: string) => {
        const at = rooms.indexOf(room);
        setRooms((r) => r.filter((x) => x !== room));
        toast(`${room} removed`, {
          action: { label: 'Undo', onClick: () => setRooms((r) => [...r.slice(0, at), room, ...r.slice(at)]) },
        });
      };
      return (
        <div className="flex min-h-20 flex-col items-start gap-3">
          <ul className="flex flex-col gap-2">
            {rooms.map((r) => (
              <li key={r} className="flex items-center gap-3 text-kh-small">
                {r}
                <Button size="sm" variant="ghost" onClick={() => remove(r)}>Remove</Button>
              </li>
            ))}
            {!rooms.length && <li className="text-kh-small text-kh-muted">No rooms left.</li>}
          </ul>
          <Toaster />
        </div>
      );
    }

API reference

PropTypeDefaultDescription
toast(title, options)(title: string, options?) => number-Shows a toast and returns its id. At most three are kept.
options.tone'neutral' | 'info' | 'danger''neutral'Danger is announced at once (role=alert).
options.descriptionstring-A second line.
options.action{ label: string; onClick: () => void }-One next step like Undo. A toast with an action is not replaced by a later one.
options.durationnumber5000Milliseconds; 0 keeps it until dismissed. A toast with an action stays until dismissed unless you set one. It never leaves while hovered or focused.
Toaster labelstring'Notifications'Accessible name of the region.
dismissToast(id), clearToasts()--Remove one toast or all of them.

Accessibility

KeyDoes
TabMoves to a toast's action and its close button
Enter / SpaceRuns the action or dismisses
EscapeDismisses the toast that has focus

A toast stays while it is hovered or focused. Danger toasts are announced at once; the others politely.

Accessibility