Status
Focused RFD discussion. The problem is open for study; the design is not
selected. No implementation is authorized or planned by this issue. This
does not create a numbered Uhura RFC, reserve the name toast, or commit Uhura
to a built-in API.
“Toast” here means an in-app transient notification, also commonly called a
snackbar or a Sonner-style toast. It does not mean an operating-system
notification or Android's system Toast.
Problem
Many applications need brief, non-blocking feedback such as “Saved”, “Upload
failed”, or “Undo delete”. In common toast systems, application code requests a
notification and moves on:
The spelling is illustrative only. The important property is that the
application does not add state solely to manage open, schedule a timeout, or
remove the notification after an exit animation.
This is not actually stateless. A long-lived notification host owns transient
instances, stable identity, ordering, timeouts, pause/resume, updates,
dismissal, announcements, and enter/exit presentation. That ownership is what
makes the authoring API feel fire-and-forget.
Uhura users can compose the visible card, but faithfully rebuilding that
lifecycle in every application is both difficult and easy to get wrong.
Requiring every app to model a queue and timer in machine state would also make
a presentation convenience look like domain state.
Current Uhura boundary
The widget taxonomy
distinguishes Element, Surface, and Pattern as primary forms, with Integration
and Behavior as cross-cutting facets. It explicitly permits first-party
capability without assuming that capability is a base-language Element.
The current 0.4 UI body is a pure projection. It cannot create callbacks,
promises, subscriptions, lifecycle effects, or browser authority
(application profile).
Pure components also create no runtime instance, state, lifecycle, context, or
scheduler; physical animation frames remain renderer state unless promoted
through a named semantic contract
(component boundary).
The deterministic kernel has no ambient clock; external work begins only after
a committed command, and later results return as declared inputs
(kernel contract).
The Instagram example demonstrates the current cost. Its notice banner needs a
machine-owned Option<Text> plus show, dismiss, and DismissNotice,
a pure NoticeBar,
and explicit conditional mounting and event wiring.
That is an honest inline banner, not a timed toast, and adding queueing,
replacement, or expiration would multiply the application-owned machinery.
Uhura already has a send-only SinkPort<T>,
which is relevant precedent for a fire-and-forget request. It does not provide
a notification host, visual adapter, queue, timing, or accessibility contract.
Those are useful constraints:
- domain-significant notice state should remain explicit machine state;
- a renderer must not smuggle semantic time or hidden machine transitions into
a visual effect;
- a pure UI declaration is not a place to run an imperative toast library; and
- the absence of a general component/lifecycle facility is not by itself proof
that Toast belongs in the intrinsic element catalogue.
The open question is whether transient notification lifetime can be owned
locally by a checked renderer/host capability, while any meaningful action or
result returns through an ordinary checked machine input.
Prior art
The useful precedent is not one visual style. It is the repeated split between
a request, a persistent host, and a host-owned lifecycle.
| System |
Ownership model |
Relevant behavior |
| Sonner |
One long-lived Toaster; toast() may be called independently of the rendered item tree |
Calls return IDs; an ID can update or dismiss a toast; loading/promise states update in place; duration may be finite or persistent; the host limits the visible stack. Its promise helper is precedent for keyed state changes, not a request to add JavaScript promises to Uhura. |
| Radix Toast |
Provider/viewport plus individually rendered controlled or uncontrolled roots |
Shared duration, pause on hover/focus/window blur, swipe dismissal, foreground/background announcement policy, and animation hooks. Radix remains low-level: authors still build an imperative event-to-toast layer when they need one. |
| React Aria Toast and React Spectrum Toast |
A toast region backed by component state or a global queue |
Stable keys, maximum visible count, overflow queue, close/pause/resume/clear, keyboard access, focus restoration, minimum timeout guidance, and no automatic timeout for actionable toasts. |
Compose SnackbarHostState |
A remembered host owns the queue and current snackbar |
showSnackbar is command-like, shows at most one item, queues concurrent calls, and completes with action-performed or dismissed. Duration may be short, long, or indefinite. |
Accessibility standards reinforce that this is more than a timer:
- WCAG status messages must be
programmatically available without moving focus.
status and alert have
different announcement urgency; ordinary updates should not all become
assertive interruptions.
- Timing Adjustable
uses a five-second toast as an example: automatic removal is safe only when
the information remains available elsewhere, or the limit can be disabled,
adjusted, or extended.
prefers-reduced-motion
separates a user's motion preference from the semantic lifetime of the
message.
Working taxonomy hypothesis
Toast probably is not one new catalog Element. At minimum it contains three
separable contracts:
- Request: a checked, command-like application affordance that asks to show,
update, or dismiss a transient notification.
- Host/region: a scoped or application-wide owner of the queue, visible
instances, placement, and accessibility region.
- Lifecycle Behavior: identity, ordering, deadlines, pause/resume,
dismissal reasons, announcement priority, and the boundary between logical
removal and exit presentation.
The visual item may then be a maintained Pattern or kit skin over that lower
contract. This would allow a first-party convenience such as toast("Saved")
without making one themed toast card part of language law.
Time and animation are not interchangeable primitives:
- a deadline determines when a presentation-only message is eligible for
dismissal;
- animation presents a lifecycle transition and must not decide semantic
removal; and
- a general machine clock is needed only if elapsed time changes machine
meaning. A renderer-local timeout should stay outside the deterministic core
when its only consequence is removing transient presentation.
A plausible lower capability is therefore not merely “timer” or “animation”.
It may be a scoped transient-host/queue contract with renderer-owned deadlines,
presence, announcement, and motion policy. This is a hypothesis for the RFD to
test, not a decision.
Design space
This RFD should compare at least:
- First-party Toast affordance. Uhura supplies the request API and a
standard unstyled host/lifecycle. Applications configure content, variant,
duration, placement, and optional checked actions.
- Headless transient-notification toolkit. Uhura supplies a queue/host API,
stable identity, timing, announcements, and conformance rules; an
application or kit supplies all visual composition.
- Lower general primitives plus a first-party package. A reusable
transient-host/presence or host-timer capability is public, and Toast is the
maintained reference composition over it.
- Explicit machine state only. Applications own every notification and
timeout. This remains the baseline to compare, but must explain how ordinary
users avoid repeatedly rebuilding queueing, accessibility, and renderer
lifecycle.
The first-party affordance and public lower toolkit are not mutually exclusive.
Lifecycle questions
A candidate should define an operational lifecycle at least as precise as:
queued -> visible <-> paused -> dismissing -> removed
| update | action / close / timeout
It should then answer:
- Where may a request originate: a machine transition, a checked application
command, a UI-event mapping, a provider result, or more than one of these?
- Is fire-and-forget the only form, or may a request return a stable key or a
typed eventual result?
- Can a key update a visible or queued notification, including
loading-to-success/error transitions? How are duplicate keys handled?
- Is the host global, per application entry, per route, per presentation, or
explicitly scoped? What happens on navigation, surface closure, hot reload,
and deployment replacement?
- Are notifications stacked or shown one at a time? What are ordering,
overflow, deduplication, replacement, and maximum-visible policies?
- Which states/variants are semantic—neutral, success, warning, error,
loading—and which are merely kit styling?
- How are default, custom, indefinite, and accessibility-adjusted durations
represented? Which interactions pause a deadline?
- What are the dismissal reasons? Is timeout observable by the machine, or
intentionally renderer-local?
- How does an action produce one checked machine input without callbacks or
hidden re-entry? Must actionable notifications remain until explicitly
handled or dismissed?
- When is a message announced politely as status, assertively as alert, or
not at all? How are rapid updates and duplicate announcements prevented?
- How does keyboard access reach a notification action without stealing
focus when the notification appears, and how is focus restored after
removal?
- What information must remain discoverable outside a timed notification?
What minimum timing, pause, dismissal, and reduced-motion guarantees are
mandatory?
- Does logical dismissal remove the notification immediately from the
semantic/accessible model while allowing renderer-owned exit motion, or is
another ordering required?
- What is the deterministic Editor representation? Static examples should be
able to pose a queue and lifecycle state without running a wall clock.
- What appears in traces and replay: show/update/dismiss requests, host
expiration inputs, neither, or a boundary selected by semantic relevance?
- What does a renderer without overlays, live regions, hover, or animation
support do? What is the immutable-export behavior?
- Which subset is general enough to support related transient UI without
turning Uhura into a generic scheduler or animation engine?
Evidence required before any implementation
A serious proposal should include:
- a small formal/operational model for queue, identity, pause, update, action,
dismissal, and disposal;
- ordinary save feedback, repeated errors, loading-to-result update, undo
action, navigation, and hot-reload examples;
- an explicit split between machine state, renderer-local state, and
Editor-only posed state;
- static Editor examples that never depend on elapsed wall time;
- accessibility cases for polite/urgent announcement, action focus, timeout,
persistent alternatives, rapid messages, and reduced motion;
- at least a Web realization and one non-Web renderer comparison;
- positive and negative checker cases for any checked request/action contract;
and
- conformance cases proving that exit animation cannot invent or delay a
machine transition.
Exit criterion
This RFD is ready to close when it records:
- Toast's taxonomy and owner boundaries;
- whether Uhura provides a first-party affordance, a headless toolkit, lower
primitives, or a layered combination;
- the request/update/dismiss/action contract without committing to raw
callbacks or promises;
- queue, lifetime, accessibility, Editor, replay, and renderer-fallback
semantics;
- whether any general time/presence/motion capability is actually required;
and
- whether the conclusion is small and durable enough to prepare as a numbered
Uhura RFC.
Non-goals
- implementing Toast in this issue;
- selecting a theme, card anatomy, icons, colors, or exact screen placement;
- designing operating-system notifications, permission prompts, blocking
alerts, or modal dialogs;
- adding ambient time, JavaScript promises, or callbacks to the deterministic
core;
- solving the complete component, Surface, animation, or general scheduler
design; or
- accepting every related transient UI pattern by analogy.
Related
Status
Focused RFD discussion. The problem is open for study; the design is not
selected. No implementation is authorized or planned by this issue. This
does not create a numbered Uhura RFC, reserve the name
toast, or commit Uhurato a built-in API.
“Toast” here means an in-app transient notification, also commonly called a
snackbar or a Sonner-style toast. It does not mean an operating-system
notification or Android's system
Toast.Problem
Many applications need brief, non-blocking feedback such as “Saved”, “Upload
failed”, or “Undo delete”. In common toast systems, application code requests a
notification and moves on:
The spelling is illustrative only. The important property is that the
application does not add state solely to manage
open, schedule a timeout, orremove the notification after an exit animation.
This is not actually stateless. A long-lived notification host owns transient
instances, stable identity, ordering, timeouts, pause/resume, updates,
dismissal, announcements, and enter/exit presentation. That ownership is what
makes the authoring API feel fire-and-forget.
Uhura users can compose the visible card, but faithfully rebuilding that
lifecycle in every application is both difficult and easy to get wrong.
Requiring every app to model a queue and timer in machine state would also make
a presentation convenience look like domain state.
Current Uhura boundary
The widget taxonomy
distinguishes Element, Surface, and Pattern as primary forms, with Integration
and Behavior as cross-cutting facets. It explicitly permits first-party
capability without assuming that capability is a base-language Element.
The current 0.4 UI body is a pure projection. It cannot create callbacks,
promises, subscriptions, lifecycle effects, or browser authority
(application profile).
Pure components also create no runtime instance, state, lifecycle, context, or
scheduler; physical animation frames remain renderer state unless promoted
through a named semantic contract
(component boundary).
The deterministic kernel has no ambient clock; external work begins only after
a committed command, and later results return as declared inputs
(kernel contract).
The Instagram example demonstrates the current cost. Its notice banner needs a
machine-owned
Option<Text>plusshow,dismiss, andDismissNotice,a pure
NoticeBar,and explicit conditional mounting and event wiring.
That is an honest inline banner, not a timed toast, and adding queueing,
replacement, or expiration would multiply the application-owned machinery.
Uhura already has a send-only
SinkPort<T>,which is relevant precedent for a fire-and-forget request. It does not provide
a notification host, visual adapter, queue, timing, or accessibility contract.
Those are useful constraints:
a visual effect;
that Toast belongs in the intrinsic element catalogue.
The open question is whether transient notification lifetime can be owned
locally by a checked renderer/host capability, while any meaningful action or
result returns through an ordinary checked machine input.
Prior art
The useful precedent is not one visual style. It is the repeated split between
a request, a persistent host, and a host-owned lifecycle.
Toaster;toast()may be called independently of the rendered item treeSnackbarHostStateshowSnackbaris command-like, shows at most one item, queues concurrent calls, and completes with action-performed or dismissed. Duration may be short, long, or indefinite.Accessibility standards reinforce that this is more than a timer:
programmatically available without moving focus.
statusandalerthavedifferent announcement urgency; ordinary updates should not all become
assertive interruptions.
uses a five-second toast as an example: automatic removal is safe only when
the information remains available elsewhere, or the limit can be disabled,
adjusted, or extended.
prefers-reduced-motionseparates a user's motion preference from the semantic lifetime of the
message.
Working taxonomy hypothesis
Toast probably is not one new catalog Element. At minimum it contains three
separable contracts:
update, or dismiss a transient notification.
instances, placement, and accessibility region.
dismissal reasons, announcement priority, and the boundary between logical
removal and exit presentation.
The visual item may then be a maintained Pattern or kit skin over that lower
contract. This would allow a first-party convenience such as
toast("Saved")without making one themed toast card part of language law.
Time and animation are not interchangeable primitives:
dismissal;
removal; and
meaning. A renderer-local timeout should stay outside the deterministic core
when its only consequence is removing transient presentation.
A plausible lower capability is therefore not merely “timer” or “animation”.
It may be a scoped transient-host/queue contract with renderer-owned deadlines,
presence, announcement, and motion policy. This is a hypothesis for the RFD to
test, not a decision.
Design space
This RFD should compare at least:
standard unstyled host/lifecycle. Applications configure content, variant,
duration, placement, and optional checked actions.
stable identity, timing, announcements, and conformance rules; an
application or kit supplies all visual composition.
transient-host/presence or host-timer capability is public, and Toast is the
maintained reference composition over it.
timeout. This remains the baseline to compare, but must explain how ordinary
users avoid repeatedly rebuilding queueing, accessibility, and renderer
lifecycle.
The first-party affordance and public lower toolkit are not mutually exclusive.
Lifecycle questions
A candidate should define an operational lifecycle at least as precise as:
It should then answer:
command, a UI-event mapping, a provider result, or more than one of these?
typed eventual result?
loading-to-success/error transitions? How are duplicate keys handled?
explicitly scoped? What happens on navigation, surface closure, hot reload,
and deployment replacement?
overflow, deduplication, replacement, and maximum-visible policies?
loading—and which are merely kit styling?
represented? Which interactions pause a deadline?
intentionally renderer-local?
hidden re-entry? Must actionable notifications remain until explicitly
handled or dismissed?
not at all? How are rapid updates and duplicate announcements prevented?
focus when the notification appears, and how is focus restored after
removal?
What minimum timing, pause, dismissal, and reduced-motion guarantees are
mandatory?
semantic/accessible model while allowing renderer-owned exit motion, or is
another ordering required?
able to pose a queue and lifecycle state without running a wall clock.
expiration inputs, neither, or a boundary selected by semantic relevance?
support do? What is the immutable-export behavior?
turning Uhura into a generic scheduler or animation engine?
Evidence required before any implementation
A serious proposal should include:
dismissal, and disposal;
action, navigation, and hot-reload examples;
Editor-only posed state;
persistent alternatives, rapid messages, and reduced motion;
and
machine transition.
Exit criterion
This RFD is ready to close when it records:
primitives, or a layered combination;
callbacks or promises;
semantics;
and
Uhura RFC.
Non-goals
alerts, or modal dialogs;
core;
design; or
Related
uiprimitive taxonomy and event-payload ownership