Feedback & status
Undo Toast
Undo notification with a countdown
detent-undo-toast
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 3
- Wave
- phase-3-overlays
- Group
- Feedback & status
7.2 KB gzip for the class module · inlines the shared spring
Variants
The toast is anchored to the viewport (like upstream), so this preview opens one on demand instead of parking all variants on the page. Hover or focus it to pause the countdown.
No toast opened yet.
Recorded states
hoverrecorded state; no setteropenrecorded state; no setterclosedrecorded state; no setterpausedrecorded state; no setterreduced motionrecorded state; no setter
Usage
Import
import '/components/undo-toast/define.js';
// or register it yourself
import { DetentUndoToast } from '/components/undo-toast/undo-toast.js';
Attributes
descriptiondurationopenpositionrestored-messageshow-progresstitleundo-labelvariant
Properties
onDismissonUndo
Slots
icon
Events
detent-undo— {}detent-dismiss— {}
Example
Runnable example: site/examples/undo-toast.ts. The snippet below is generated from the catalogued attributes.
<detent-undo-toast
description="Changes will be permanent in a few seconds"
duration="5000"
open
position="bottom-center"
restored-message="Restored successfully"
show-progress
title="Project archived"
undo-label="Undo"
variant="default"
></detent-undo-toast>
Property-only values
const element = document.querySelector('detent-undo-toast');
if (element !== null) {
// onUndo: () => void
element.onUndo = () => {};
// onDismiss: () => void
element.onDismiss = () => {};
}
Source
The pinned upstream source for this row:
src/components/ui/UndoToast.tsxsha256 658830774bff…
Native files
The native module and its artifacts:
src/components/undo-toast/undo-toast.tssrc/components/undo-toast/styles.csssrc/components/undo-toast/define.tsdocs/components/undo-toast.mdsite/examples/undo-toast.tstests/browser/undo-toast.spec.tsevidence/components/undo-toast.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
description | description | string | 'Changes will be permanent in a few seconds' | Kebab-case attribute; camelCase property. |
duration | duration | number | 5000 | Kebab-case attribute; camelCase property. |
open | open | boolean | true | Presence semantics: present means true, absent means false. |
position | position | 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' | 'bottom-center' | Kebab-case attribute; camelCase property. |
restored-message | restoredMessage | string | 'Restored successfully' | Kebab-case attribute; camelCase property. |
show-progress | showProgress | boolean | true | Presence semantics: present means true, absent means false. |
title | title | string | 'Project archived' | Kebab-case attribute; camelCase property. |
undo-label | undoLabel | string | 'Undo' | Kebab-case attribute; camelCase property. |
variant | variant | 'default' | 'success' | 'warning' | 'error' | 'info' | 'default' | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
onDismiss | () => void | undefined | Property-only: assign from script; not accepted as an attribute. |
onUndo | () => void | undefined | Property-only: assign from script; not accepted as an attribute. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-undo | onUndo | () => void | Composed, bubbles; fires only for a user action. |
detent-dismiss | onDismiss | () => void | Composed, bubbles; fires only for a user action. |
Slots
| Slot | Notes |
|---|---|
icon | Consumer content; rendered through a slot, never re-created. |
CSS customisation
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- onUndo/onDismiss become detent-undo/detent-dismiss with detail: {}; className becomes parts and custom properties; icon becomes the icon slot.
- The toast is position: fixed inside the host, not in the top layer. A native dialog would make it modal and a popover would light-dismiss it on an outside click; neither matches upstream. Recorded limitation: an ancestor with transform, filter, contain or perspective becomes the containing block, so the toast would be anchored to that ancestor instead of the viewport (PC-9.8).
- Framer Motion's springSnappy entry/exit becomes the shared spring helper with the same pinned values (380/30/0.5) driving one progress value, so an interrupted exit reverses from where it is. Entry offsets (±16 px) and exit offsets (±10 px, scale 0.94) match upstream. Reduced motion skips both.
- The countdown is a direct requestAnimationFrame width update instead of upstream's transition: width 50ms linear on a state-driven percentage; the bar still shrinks monotonically and freezes on pause.
- lucide-react icons (Check, X, AlertTriangle, AlertCircle, Info, Trash2, Undo2) become local decorative inline SVGs (upstream ISC notice retained in THIRD_PARTY_NOTICES.md).
- icon={false} has no attribute form: an empty icon slot suppresses the variant icon instead (the row records icon as a slot).
- Accessibility additions: Escape inside the toast dismisses it, focus inside pauses the countdown, a dismissal moves focus out of the toast, and the close control's focus ring comes from the --detent-focus-ring token instead of a Tailwind focus-ring utility.
- The position attribute is reflected for styling (:host([position=…])), which is why the row's position entry reads as both an attribute and a property.
What it does
A toast with a real-time countdown bar, pause on hover, a restored state after Undo, six screen positions and five semantic variants.
Upstream behaviour
- Real-time smooth countdown progress bar illustrating time window remaining
- Intelligent pause-on-hover physics so users never miss undo deadlines
- Visual state morphing upon clicking Undo before gentle dismissal
- Configurable multi-corner positioning (top, bottom, center, corners)
- Multiple semantically tinted variants: Default, Success, Warning, Error, and Info
Differences from upstream
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- onUndo/onDismiss become detent-undo/detent-dismiss with detail: {}; className becomes parts and custom properties; icon becomes the icon slot.
- The toast is position: fixed inside the host, not in the top layer. A native dialog would make it modal and a popover would light-dismiss it on an outside click; neither matches upstream. Recorded limitation: an ancestor with transform, filter, contain or perspective becomes the containing block, so the toast would be anchored to that ancestor instead of the viewport (PC-9.8).
- Framer Motion's springSnappy entry/exit becomes the shared spring helper with the same pinned values (380/30/0.5) driving one progress value, so an interrupted exit reverses from where it is. Entry offsets (±16 px) and exit offsets (±10 px, scale 0.94) match upstream. Reduced motion skips both.
- The countdown is a direct requestAnimationFrame width update instead of upstream's transition: width 50ms linear on a state-driven percentage; the bar still shrinks monotonically and freezes on pause.
- lucide-react icons (Check, X, AlertTriangle, AlertCircle, Info, Trash2, Undo2) become local decorative inline SVGs (upstream ISC notice retained in THIRD_PARTY_NOTICES.md).
- icon={false} has no attribute form: an empty icon slot suppresses the variant icon instead (the row records icon as a slot).
- Accessibility additions: Escape inside the toast dismisses it, focus inside pauses the countdown, a dismissal moves focus out of the toast, and the close control's focus ring comes from the --detent-focus-ring token instead of a Tailwind focus-ring utility.
- The position attribute is reflected for styling (:host([position=…])), which is why the row's position entry reads as both an attribute and a property.