Docyrus

NotificationStack

Swipe-to-dismiss stack of notification cards with a skip button, auto-dismissing actions and automatic load-more.

iOSAndroid
Preview NotificationStack on your device

Scan with Expo Go

Download Expo Go, then scan the QR code to preview native components.

Installation

pnpm dlx @docyrus/cli add @docyrus/rn-notification-stack
Required Packages(4 packages)
pnpm add react-native-gesture-handler react-native-reanimated react-native-worklets react-native-svg

The stack uses react-native-gesture-handler, so the app must be wrapped in <GestureHandlerRootView> (Expo Router apps already are).

Usage

import { NotificationStack } from '@/components/docyrus-native/notification-stack';
import type { NotificationItem } from '@/components/docyrus-native/notification-stack';

<NotificationStack
  notifications={unread}
  onDismiss={id => markAsRead(id)}
  onClick={id => openNotification(id)}
  onAction={(id, action) => runAction(id, action)}
  onLoadMore={() => fetchNextPage()}
/>

The top card is interactive; the cards behind it are scaled down (5 % per level), offset 10 px lower and faded (15 % per level), and the container reserves bottom padding for those offsets. Dismissing the top card:

  • Swipe it left or right past 100 px, or fling it faster than 500 px/s — it rotates (±8°) and fades while dragged, then flies out. A shorter swipe springs back. Vertical drags are ignored so a parent ScrollView keeps scrolling.
  • Skip it with the ✕ button in its top-right corner.
  • Act on it — pressing an action button fires the action's onClick, then onAction, then dismisses the card.

Every dismissal calls onDismiss(id). The stack tracks dismissed ids internally, so it works without removing items from notifications. When the remaining cards drop to loadMoreThreshold, onLoadMore fires once; it re-arms after more notifications arrive. New cards enter with a fade-down, dismissed ones leave with a fade-right (Reanimated layout animations).

Single card

import { NotificationStackCard } from '@/components/docyrus-native/notification-stack';

<NotificationStackCard
  notification={notification}
  onDismiss={id => remove(id)}
  onAction={(id, action) => handle(id, action)}
/>

API Reference

NotificationStackProps

PropTypeDefaultDescription
notificationsNotificationItem[]—Notifications to stack (see NotificationsPanel → NotificationItem).
maxVisiblenumber10Maximum cards rendered in the stack.
loadMoreThresholdnumber2When the remaining (not dismissed) card count reaches this value, onLoadMore fires once.
onLoadMore() => void—Fetch more notifications.
onDismiss(id: string) => void—Fired on swipe, skip button, or after an action.
onClick(id: string) => void—Fired when the top card body is pressed.
onAction(id: string, action: string) => void—Fired with the action label; the card is auto-dismissed afterwards.
variant'default' | 'bordered''default'bordered gives every card a medium shadow.
size'sm' | 'default' | 'lg''default'Card padding, icon size and text size.
emptyStateReactNodedashed "No notifications" boxShown when no cards remain. Default text: t('ui.notificationStack.empty', 'No notifications').
classNamestring—Additional classes for the root (max width 672, centred).

NotificationStackCardProps

PropTypeDefaultDescription
notificationNotificationItem—The notification to render.
size'sm' | 'default' | 'lg''default'Card density.
onDismiss(id: string) => void—Swipe / skip callback. The ✕ button is only rendered when this is set (accessibility label t('ui.notificationStack.dismiss', 'Dismiss')).
onClick(id: string) => void—Card body press.
onAction(id: string, action: string) => void—Action button press.
swipeEnabledbooleantrueEnable swipe-to-dismiss (the stack enables it on the top card only).
classNamestring—Additional card classes.

Components

Component / exportDescription
NotificationStackThe stack.
NotificationStackCardOne swipeable card (web parity export).
notificationStackVariantstv() slots (base, card) for the stack.
notificationStackCardVariantstv() slots for the card (base, icon, badge, title, text, timestamp) by size.

Type Exports

TypeDescription
NotificationStackPropsProps for NotificationStack.
NotificationStackCardPropsProps for NotificationStackCard.
NotificationItemRe-exported from notifications-panel (now carries badge: ReactNode).
NotificationActionRe-exported from notifications-panel (now carries icon: ReactNode).
NotificationStackVariant'default' | 'bordered'.
NotificationStackSize'sm' | 'default' | 'lg'.
StackNotificationItemDeprecated alias of NotificationItem.
StackNotificationActionDeprecated alias of NotificationAction.

Breaking changes

  • Now a multi-file component (components/notification-stack/). The import path @/components/docyrus-native/notification-stack is unchanged; reinstall with docyrus add rn-notification-stack so the old single file is replaced by the folder.
  • maxVisible default 3 → 10 (web parity), and it now caps the rendered stack instead of a list with "N more".
  • The "N more" link under the stack was removed — onLoadMore fires automatically once the remaining cards reach loadMoreThreshold (and only once until more arrive, previously on every change).
  • Actions now auto-dismiss the card and call onDismiss (web parity).
  • Stacked-card visual replaces the front card + edge shells; the dismiss button moved to the card's top-right corner and cards can be swiped away.
  • StackNotificationItem / StackNotificationAction are deprecated aliases — badge is now ReactNode (was string | number) and lives on NotificationItem itself, and the badge sits at the bottom-right of the icon in the type colour (was a red top-right counter).
  • Requires react-native-worklets (already a peer of Reanimated 4) and a GestureHandlerRootView ancestor.

On this page