Skip to content

feat: add the Toast primitive#14

Open
ivanbanov wants to merge 2 commits into
mainfrom
feat/toast
Open

feat: add the Toast primitive#14
ivanbanov wants to merge 2 commits into
mainfrom
feat/toast

Conversation

@ivanbanov

Copy link
Copy Markdown
Member

Summary

Adds the Toast primitive as two packages:

  • @dunky.dev/toast — the framework-free core: state machine, connect, and the behavior contract.
  • @dunky.dev/react-toast — the thin React binding (compound components over the core machine).

A declarative, Radix-style toast — one component per toast, not an imperative toaster store. Each toast owns its open/close state (controlled and uncontrolled), announces as a status live region, auto-dismisses after its duration (Infinity = persistent), and pauses every timer while the viewport is hovered or focused. Resuming restarts the full duration — a documented deviation from Radix that keeps the timer contract inside the state machine.

Anatomy

<Toast.Provider label='Notifications' duration={5000}>
  <Toast.Viewport>
    <Toast>
      <Toast.Root>
        <Toast.Title />
        <Toast.Description />
        <Toast.Action />
        <Toast.Close />
      </Toast.Root>
    </Toast>
  </Toast.Viewport>
</Toast.Provider>

Behavior

  • Root is role="status" with politeness driven by the toast type: foreground -> aria-live="assertive", background -> aria-live="polite"; aria-atomic announces the whole toast on update.
  • Showing a toast never steals focus — its controls sit in the normal tab order through the viewport (role="region" landmark labelled by the provider's label, toasts as a list inside it).
  • Hover or focus on the viewport pauses every toast's timer; leaving/blurring resumes with the full duration.
  • Dismissing the toast that holds keyboard focus parks focus on the viewport (tabindex="-1"), so the user keeps their place and the focus-derived pause stays truthful.
  • Root is labelled by the rendered Title and described by the rendered Description — an omitted part never leaves a dangling ARIA reference.
  • Action and Close dismiss from inside; every open/close intent (including auto-dismiss) is reported through onOpenChange.

Test plan

  • pnpm test:ci — 119 tests, 9 files, all passing
  • pnpm typecheck
  • pnpm lint
  • pnpm format:check
  • pnpm build

🤖 Generated with Claude Code

Anatomy: Toast.Provider / Toast.Viewport / Toast (Root, Title,
Description, Action, Close). A status live-region toast that
auto-dismisses after its duration and pauses timers while the
viewport is hovered or focused.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ivanbanov ivanbanov changed the title feat: add the Toast primitive feat(react): add the Toast primitive Jul 18, 2026
@ivanbanov ivanbanov changed the title feat(react): add the Toast primitive feat: add the Toast primitive Jul 18, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant