Feedback & status
Toast Stack
Swipe-to-dismiss toast stack
detent-toast-stack
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 3
- Wave
- phase-3-overlays
- Group
- Feedback & status
8.8 KB gzip for the class module · inlines the shared spring
Variants
Recorded states
closedrecorded state; no setterdraggingrecorded state; no setter
Usage
Import
import '/components/toast-stack/define.js';
// or register it yourself
import { DetentToastStack } from '/components/toast-stack/toast-stack.js';
Attributes
max-visibleclosedexpandeddragging
Properties
initialNotifications
Slots
No slots are recorded for this component.
Events
No events are recorded for this component.
Example
Runnable example: site/examples/toast-stack.ts. The snippet below is generated from the catalogued attributes.
<detent-toast-stack
max-visible="3"
closed
expanded
dragging
></detent-toast-stack>
Property-only values
const element = document.querySelector('detent-toast-stack');
if (element !== null) {
// initialNotifications: NotificationItem[]
element.initialNotifications = [];
}
Source
The pinned upstream source for this row:
src/components/ui/NotificationStack.tsxsha256 89664571b64b…
Native files
The native module and its artifacts:
src/components/toast-stack/toast-stack.tssrc/components/toast-stack/styles.csssrc/components/toast-stack/define.tsdocs/components/toast-stack.mdsite/examples/toast-stack.tstests/browser/toast-stack.spec.tsevidence/components/toast-stack.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 |
|---|---|---|---|---|
max-visible | maxVisible | number | 3 | Kebab-case attribute; camelCase property. |
closed | — | — | — | Recorded in the catalog as an attribute. |
expanded | — | — | — | Recorded in the catalog as an attribute. |
dragging | — | — | — | Recorded in the catalog as an attribute. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
initialNotifications | NotificationItem[] | [] | Property-only: assign from script; not accepted as an attribute. |
Events
No custom events are recorded for this component.
Slots
No slots are recorded for this component.
CSS customisation
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- className is replaced by the documented parts and custom properties.
- React state and AnimatePresence do not exist: the element owns the list, and the row records no events, so no callback or event was invented. The only announcement is the polite status region the row's accessibility note asks for (upstream declared one in its meta but never implemented it).
- Framer Motion drag physics become a direct pointer-follow offset plus the shared spring helper with the pinned upstream springResponsive token (320/24/0.7) for the rebound. Reduced motion applies the rest value with no frames.
- The exit (opacity 0, x: 80, scale: 0.9) runs on springResponsive (320/24/0.7) through the shared spring helper, with opacity clamped to [0, 1], as upstream's AnimatePresence popLayout: the item leaves the list at once, its card leaves the layout and becomes inert (focus moves to the next card's dismiss button or the toggle) and is removed when the spring settles. It is skipped under reduced motion; a disconnect removes it.
- Cards are keyed by id and every card's offset, scale and opacity springs on springResponsive to its slot on add, dismiss and slice changes, entering from upstream's initial { opacity: 0, y: -20, scale: 0.95 } (first paint included, as upstream sets no initial={false}). The empty placeholder fades in on framer-motion's default 0.3 s opacity tween. Expand and Stack glide each card between its stack slot and its column place on springResponsive (Framer layout, vertical position only, through src/motion/layout-flip.ts), as do column-mode adds and dismisses.
- Expanded mode lays the cards out in a normal column with a gap instead of the upstream fixed y: index * 74 offset, which overlapped cards taller than 74 px.
- “+ Simulate” adds one of three canned notifications in rotation instead of a Math.random() choice, so the behaviour is reproducible and testable.
- lucide-react icons (CheckCircle2, AlertCircle, Info, X, Bell) become local decorative inline SVGs (upstream ISC notice retained in THIRD_PARTY_NOTICES.md).
- Accessibility corrections: the dismiss button carries an explicit focus ring, the counter/type information is exposed as text (upstream used heading levels inside a card list, which cannot be given a level from inside a component safely), and the new status region announces add/dismiss once.
What it does
A stack of notification cards: the newest card sits on top, the others are offset and scaled behind it, a card can be swiped to the right to dismiss it, and an “Expand” control reveals the whole history.
Upstream behaviour
- Interactive drag-to-dismiss with spring rebound
- Dynamic stacking elevation offset and scale
- Expandable history view
Differences from upstream
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- className is replaced by the documented parts and custom properties.
- React state and AnimatePresence do not exist: the element owns the list, and the row records no events, so no callback or event was invented. The only announcement is the polite status region the row's accessibility note asks for (upstream declared one in its meta but never implemented it).
- Framer Motion drag physics become a direct pointer-follow offset plus the shared spring helper with the pinned upstream springResponsive token (320/24/0.7) for the rebound. Reduced motion applies the rest value with no frames.
- The exit (opacity 0, x: 80, scale: 0.9) runs on springResponsive (320/24/0.7) through the shared spring helper, with opacity clamped to [0, 1], as upstream's AnimatePresence popLayout: the item leaves the list at once, its card leaves the layout and becomes inert (focus moves to the next card's dismiss button or the toggle) and is removed when the spring settles. It is skipped under reduced motion; a disconnect removes it.
- Cards are keyed by id and every card's offset, scale and opacity springs on springResponsive to its slot on add, dismiss and slice changes, entering from upstream's initial { opacity: 0, y: -20, scale: 0.95 } (first paint included, as upstream sets no initial={false}). The empty placeholder fades in on framer-motion's default 0.3 s opacity tween. Expand and Stack glide each card between its stack slot and its column place on springResponsive (Framer layout, vertical position only, through src/motion/layout-flip.ts), as do column-mode adds and dismisses.
- Expanded mode lays the cards out in a normal column with a gap instead of the upstream fixed y: index * 74 offset, which overlapped cards taller than 74 px.
- “+ Simulate” adds one of three canned notifications in rotation instead of a Math.random() choice, so the behaviour is reproducible and testable.
- lucide-react icons (CheckCircle2, AlertCircle, Info, X, Bell) become local decorative inline SVGs (upstream ISC notice retained in THIRD_PARTY_NOTICES.md).
- Accessibility corrections: the dismiss button carries an explicit focus ring, the counter/type information is exposed as text (upstream used heading levels inside a card list, which cannot be given a level from inside a component safely), and the new status region announces add/dismiss once.