Detent web components
By Shane Logsdon

Feedback & status

Undo Toast

Implemented

Undo notification with a countdown

detent-undo-toast

Port status
Catalog status
implemented
Origin
upstream
Phase
3
Wave
phase-3-overlays
Group
Feedback & status
Show undo toast

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.

success · bottom-left warning · top-right error · top-left info · top-center

No toast opened yet.

Recorded states

  • hover recorded state; no setter
  • open recorded state; no setter
  • closed recorded state; no setter
  • paused recorded state; no setter
  • reduced motion recorded 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

  • description
  • duration
  • open
  • position
  • restored-message
  • show-progress
  • title
  • undo-label
  • variant

Properties

  • onDismiss
  • onUndo

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.tsx sha256 658830774bff…

Native files

The native module and its artifacts:

  • src/components/undo-toast/undo-toast.ts
  • src/components/undo-toast/styles.css
  • src/components/undo-toast/define.ts
  • docs/components/undo-toast.md
  • site/examples/undo-toast.ts
  • tests/browser/undo-toast.spec.ts
  • evidence/components/undo-toast.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
descriptiondescriptionstring'Changes will be permanent in a few seconds'Kebab-case attribute; camelCase property.
durationdurationnumber5000Kebab-case attribute; camelCase property.
openopenbooleantruePresence semantics: present means true, absent means false.
positionposition'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right''bottom-center'Kebab-case attribute; camelCase property.
restored-messagerestoredMessagestring'Restored successfully'Kebab-case attribute; camelCase property.
show-progressshowProgressbooleantruePresence semantics: present means true, absent means false.
titletitlestring'Project archived'Kebab-case attribute; camelCase property.
undo-labelundoLabelstring'Undo'Kebab-case attribute; camelCase property.
variantvariant'default' | 'success' | 'warning' | 'error' | 'info''default'Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
onDismiss() => voidundefinedProperty-only: assign from script; not accepted as an attribute.
onUndo() => voidundefinedProperty-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-undoonUndo() => voidComposed, bubbles; fires only for a user action.
detent-dismissonDismiss() => voidComposed, bubbles; fires only for a user action.

Slots

Slots
SlotNotes
iconConsumer 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.
Previous: Typewriter Button Next: Accordion

Docs

Components

Project