Pattern · Lists and Feeds

New

Notification Inbox SwiftUI Block

An in-app notifications list: All, Mentions and Unread, day groups, inline Accept and Reply, swipe actions and Mark all as read.

The notifications list of a workspace, social or commerce app. Rows group into New, Earlier this week and Older, each with the actor's photo or initials, one sentence with the names in semibold, the time and an unread dot, and invites, mentions and shares carry inline buttons that show working, done and failed in place. Swipe or long-press to mark read, unread or delete, and Mark all as read clears every dot with a rolling count.

  • SwiftUI
  • iOS 17.0+
  • notification-inbox
Get SwiftPieces Pro

$199 once, every piece included. See how installing works · Sign in

01

What's included

SwiftUI source you own, built on Apple frameworks only.

  • All, Mentions and Unread with the unread count
  • New, Earlier this week and Older groups on calm cards
  • Accept, Decline, Reply and View with per-row working, done and failed
  • Swipe or long-press to mark read, unread or delete
  • Mark all as read, empty states per filter and loading shapes

02

Implementation notes

How it behaves, what it handles, and the decisions behind it.

A filter at the top switches between All, Mentions and Unread, the Unread segment carrying its count, and a line under it reads "3 unread" beside Mark all as read. Notifications group into New (the last 24 hours), Earlier this week and Older, each on a surface card. A row leads with the actor's photo or initials, or a symbol for notices from the app, then one sentence with the actor and the subject in semibold ("Maya Chen invited you to Design review"), an optional preview line, the time ("12m", "3h", "Tue", "Oct 12") and an accent dot while it is unread; read rows step their sentence back to the secondary ink. Invites carry Accept and Decline, mentions Reply and shares View: the pressed button shows a spinner while your closure runs and a checkmark when it returns, then Accept and Decline settle into the resolution your model sets ("You're going"); a thrown error shows under the row. Swiping a row left reveals Read or Unread and Delete, and a long press offers the same menu. Mark all as read changes every row at once, rolls the count down to "All caught up" and puts things back with an error line if your closure throws. Each filter has its own empty state ("You're all caught up" under Unread), and passing nil shows grey shapes while the inbox loads.

Notes

  • NotificationInbox(notifications:initialFilter:onAction:onMarkRead:onDelete:onOpen:) takes the inbox and two required closures. It is a section: no navigation stack, background, padding or scroll view of its own, so place it in your own ScrollView column with .padding(.horizontal, 20).
  • NotificationInbox.Item holds an id, a Kind (.mention, .reply, .invite, .share, .reaction, .follow, .system), the actor, the message between actor and subject ("invited you to"), the subject, an optional preview, the date, isRead, the inline actions, an optional resolution and an optional avatar (.image(URL) through AsyncImage, or .symbol(name)). Without an avatar the row shows the actor's initials, or the kind's symbol when there is no actor. Item.samples is the inbox from the preview.
  • Mentions and replies show under the Mentions filter. A row stays under Unread while its action is running, so it doesn't vanish mid-press.
  • onAction(item, action) is async throws and runs for .accept, .decline, .reply and .view. Accept is the row's one accent button; the others sit on fill. Accept and Decline show a checkmark for a moment, then the row shows item.resolution or, until you set one, "Accepted" or "Declined". Reply and View return to rest when the closure returns, so navigate from inside it. CancellationError backs out quietly; any other error shows its errorDescription (or "That didn't go through. Try again.") under the row.
  • onMarkRead(ids, read) covers a single row (swipe, menu or tap) and Mark all as read. The block shows the change before the closure returns and keeps it until your model agrees; a thrown error puts the old state back and says so.
  • onDelete removes the row at once and brings it back with an error if it throws. Leave it out to hide Delete from the swipe and the menu.
  • onOpen makes rows tappable: the block marks the row read and calls you to open what it points at. A tap on a row that is swiped open closes it instead.
  • The swipe only claims a drag that is clearly horizontal, so the host's scroll view keeps scrolling.
  • Haptics mark a selection when the filter changes, success when an action or Mark all finishes, and an error when one fails.
  • VoiceOver reads each row as one sentence with "Unread", the preview and a relative time ("12 minutes ago"), with Mark as read or unread and Delete as actions; results are announced. At accessibility sizes the avatar steps aside and previews no longer truncate.
  • Reduce Motion turns every change into a short fade.
  • NotificationInboxExample at the end of NotificationInbox.swift is the Notifications screen in the preview: a team workspace's inbox where accepting the invite settles on "You're going" and Mark all as read clears every dot. Pass wrapsInNavigationStack: false to push it in your own stack.

Theming

  • Reads SPTheme from @Environment(\.spTheme) and requires the design-system item.
  • Cards are color.surface with radius.lg, SPHairline dividers in separator inset to the text, and groups 24 points apart (space.xl). Group titles are type.headline.
  • The filter is a fill track at radius.sm with the selected segment on surfaceElevated.
  • Sentences are type.subhead with names in semibold textPrimary; previews and times are type.footnote in textSecondary and textTertiary. Avatars are 40 point circles on fill with textSecondary initials or symbols.
  • The unread dot and Accept are accent (Accept with accentOn text); other inline buttons are fill with textPrimary. Swipe actions are neutral and danger with statusOn. Errors are danger.
  • Changes animate with motion.smooth and motion.snappy, and become short fades under Reduce Motion.