Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelog
  1. Home
  2. Blog
  3. Component guides
  4. React Notification Center: Inbox, Bell and Toasts
Auf Deutsch lesen
  1. Blog
  2. Component guides
  3. React Notification Center: Inbox, Bell and Toasts
Component guides
Component guides

React Notification Center: Inbox, Bell and Toasts

SR
Serban Rusu · Founder of Wingo UI
Oct 19, 2026 · 8 min read

On this page

0%
  1. What is a React notification center?
  2. When does an event need a toast, the inbox or both?
  3. What are the options for a React notification center?
  4. What props does the notification center need?
  5. How do read, unread and dismiss stay in sync with a server?
    1. Should the bell count unread or unseen?
  6. How do filter tabs and day groups work?
  7. How does a notification panel work on a phone?
  8. Is the notification center accessible?
  9. How do I add real-time notifications and toasts?
  10. Can I use just the bell, or a full notifications page?
  11. What are the common notification center pitfalls?
  12. How do I add it to my project?
  13. FAQ
    1. What is the difference between a toast and a notification center?
    2. Should the bell count unread or unseen notifications?
    3. How do I get real-time notifications into a React inbox?
    4. Do I need Novu or Knock to build a notification center?
    5. Is the Wingo UI Notification Center free?

TL;DR

A React notification center is a bell with an unread count that opens a persistent inbox: filter tabs, day groups, mark as read, inline actions and a way to dismiss rows. Every event that matters goes into the inbox, and only the ones that need attention right now also show a toast. The Wingo UI Notification Center takes a notifications array and read and dismiss callbacks, and turns into a full-height bottom sheet with swipe actions on phones.

Published Oct 19, 2026

A React notification center is the bell in your top bar and the inbox behind it: an unread count, a list of what happened, a way to mark things read and a way to get rid of them. Most notification panel React examples stop at a popover with a list. Then real data arrives: forty rows that need grouping, a mention buried under billing events, an invite that needs an Accept button, a phone where the popover covers half the screen. This guide covers when an event belongs in the inbox and when in a toast, the props that matter in the Wingo UI Notification Center, syncing reads with a server, phones, accessibility and real-time updates.

What is a React notification center?

It is a persistent, in-app record of events, opened from a bell. An in-app notification inbox does four jobs that a plain list does not:

  1. Counts. A badge on the bell with the number of unread items, capped (99+), hidden at zero.
  2. Sorts. Filter tabs (All, Unread, Mentions) and day groups (Today, Yesterday, then dates), newest first.
  3. Acts. Open a row, accept an invite inline, mark read or unread, dismiss.
  4. Updates. New rows slide in, the bell reacts, older pages load as you scroll.

When does an event need a toast, the inbox or both?

Use a toast for feedback on something the user just did, and the inbox for anything they may need later. Courier's guide to toasts in web apps (opens in a new tab) puts it as a rule worth keeping: every important event goes to the notification center, and only some of them also get a toast. GitHub goes further: its Primer accessibility guidance (opens in a new tab) says toasts pose significant accessibility concerns and recommends banners and dialogs instead. That is a reason to never put anything only in a toast.

EventToastInbox
"Settings saved" after a clickYesNo
A teammate mentions youYes, while the app is openYes
An invoice was paid overnightNoYes
A payment failedYesYes, with a Retry action
An export finished in the backgroundYes, with an Open actionYes

For choosing the toast itself, see the Sonner alternative comparison; the free Wingo UI Toast is the one used in the examples below.

What are the options for a React notification center?

You either use a hosted service that stores and delivers notifications for you, or a UI component you feed your own data. As of October 2026:

OptionWhat it isWhere notifications live
Novu Inbox (opens in a new tab)<Inbox> component with a bell, configured with applicationIdentifier and subscriber; renderBell gets unreadCountNovu's service
Knock (opens in a new tab)React SDK with a NotificationIconButton whose badgeCountType is unseen, unread or allKnock's service
Wingo UI Notification CenterPro source component, npx wingo-ui@latest add notification-centerYour database, passed in as an array

Pick Novu or Knock when you need delivery across email, push and in-app with user preferences: that is a backend problem a component does not solve. Pick a source component when your app already stores notifications.

What props does the notification center need?

One array and the callbacks that change it. notifications is newest first; each item has an id, a title, a createdAt (Date, string or number) and optional body, read, type, avatar with name, or an icon with a tone, an href and inline actions.

bash
npx wingo-ui@latest add notification-center
tsx
"use client";
import { useState } from "react";
import { CreditCard } from "lucide-react";
import { NotificationCenter, type NotificationItem } from "@/components/ui/notification-center";
const initial: NotificationItem[] = [
{
id: "n1",
type: "mention",
name: "Sophia Lee",
title: "Sophia Lee mentioned you in “Acme Corp quote”",
body: "@Emma Carter can you check the discount before we send it?",
createdAt: "2026-10-19T10:42:00",
href: "/quotes/Q-2026-118",
},
{
id: "n2",
icon: <CreditCard />,
tone: "danger",
title: "Your Pro plan payment failed",
body: "The card ending in 4821 was declined.",
createdAt: "2026-10-19T08:15:00",
actions: [{ label: "Retry", onSelect: () => fetch("/api/billing/retry", { method: "POST" }) }],
},
];
export function TopbarNotifications() {
const [items, setItems] = useState(initial);
return (
<NotificationCenter
notifications={items}
onRead={(ids) => setItems((prev) => prev.map((n) => (ids.includes(n.id) ? { ...n, read: true } : n)))}
onUnread={(ids) => setItems((prev) => prev.map((n) => (ids.includes(n.id) ? { ...n, read: false } : n)))}
onDismiss={(id) => setItems((prev) => prev.filter((n) => n.id !== id))}
settingsHref="/settings/notifications"
locale="en-US"
/>
);
}

type: "mention" feeds the Mentions tab, and @Name inside string titles and bodies is highlighted. An action whose onSelect returns a promise keeps its button spinning until it settles. Rows with an href render through the router link you set with LinkProvider or the linkComponent prop, so next/link works without the component importing it.

Open the bell in the demo, then press Simulate a notification: the bell swings and the row slides in.

Open the bell, mark a row read from its ⋯ menu, accept the invite, then press Simulate a notification and watch the count go up
$ npx wingo-ui@latest add notification-center
ProNotification Center docs

How do read, unread and dismiss stay in sync with a server?

The panel updates at once and calls your handler; you save the change. The component keeps that local state until your notifications data agrees, so a slow request never makes a dot blink back on. onReadAll runs for the header button; without it, onRead gets every unread id. onDismiss fires after the row has collapsed out, which is the moment to remove it from your data.

tsx
const save = (body: object) =>
fetch("/api/notifications", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
<NotificationCenter
notifications={items}
onRead={(ids) => save({ ids, read: true })}
onUnread={(ids) => save({ ids, read: false })}
onReadAll={() => save({ all: true, read: true })}
onDismiss={(id) => {
setItems((prev) => prev.filter((n) => n.id !== id));
fetch(`/api/notifications/${id}`, { method: "DELETE" });
}}
hasMore={hasMore}
onLoadMore={loadOlder}
/>

hasMore adds a Load more row that also loads by itself when it scrolls into view. Return a promise from onLoadMore and the row shows a spinner until it settles.

Should the bell count unread or unseen?

The badge counts unread items by default. Some apps prefer unseen: the count resets when the panel opens, even if nothing was read. Knock exposes the same choice through badgeCountType. Here, count takes any number:

tsx
const [seenAt, setSeenAt] = useState(0);
const unseen = items.filter((n) => !n.read && new Date(n.createdAt).getTime() > seenAt).length;
<NotificationCenter notifications={items} count={unseen} onOpenChange={(open) => open && setSeenAt(Date.now())} />

How do filter tabs and day groups work?

filters replaces the default All / Unread / Mentions tabs. Each tab has a value, a label, an optional filter function, a badge and its own empty state. A tab whose value is "unread" shows the live unread count without a badge.

tsx
filters={[
{ value: "all", label: "All" },
{ value: "billing", label: "Billing", filter: (n) => n.type === "billing", badge: unreadBilling },
{ value: "mentions", label: "Mentions", filter: (n) => n.type === "mention", emptyTitle: "No mentions" },
]}

Fewer than two filters hide the tab row. groupByDay (on by default) adds sticky labels: Today, Yesterday, weekday names this week, then dates in the locale. Times read "5 min ago" through Intl.RelativeTimeFormat and refresh every minute while the panel is open. Relative times are computed after mount and the server prints clock times, so there is no hydration mismatch.

How does a notification panel work on a phone?

Below 768px the panel becomes a full-height bottom sheet with full-width tabs, because a 400px popover anchored to a small bell leaves no room on a 390px screen. Drag the handle down to close it. Swipe a row left to dismiss it or right to toggle read: a colored layer follows your finger, the swipe commits past a quarter of the row or on a flick, and the phone gives a short haptic tick where the browser supports vibration. The ⋯ menu is always visible on touch, so swiping is never the only way. responsive={false} keeps the popover, swipeActions={false} turns swiping off. The gesture is the same one covered in the React swipe to delete guide, and the sheet is the pattern from the React bottom sheet guide.

Is the notification center accessible?

The bell's label carries the count ("Notifications, 3 unread"), and it changes as the count does. New notifications that arrive while the component is mounted are announced through a polite live region ("New notification: ..."), and older pages from Load more are not announced as news. With the panel open, Down or Home jumps to the first row, Up and Down move between rows, End jumps to the last, Enter opens one, and Escape closes the panel and returns focus to the bell. The row menu shows on hover and on keyboard focus. shortcut="alt+n" opens the panel from anywhere, and every string lives in labels for translation.

How do I add real-time notifications and toasts?

Stream new notifications into the same array, and toast only the urgent ones while the panel is closed. This example uses Server-Sent Events with the browser's EventSource; a WebSocket works the same way. Mount <Toaster /> once in your root layout.

tsx
"use client";
import { useEffect, useRef, useState } from "react";
import Link from "next/link";
import { NotificationCenter, type NotificationItem } from "@/components/ui/notification-center";
import { toast } from "@/components/ui/toast";
export function LiveNotifications({ initial }: { initial: NotificationItem[] }) {
const [items, setItems] = useState(initial);
const [open, setOpen] = useState(false);
const openRef = useRef(false);
useEffect(() => {
const source = new EventSource("/api/notifications/stream");
source.onmessage = (event) => {
const item = JSON.parse(event.data) as NotificationItem;
setItems((prev) => (prev.some((n) => n.id === item.id) ? prev : [item, ...prev]));
// the inbox always gets it; a toast only for mentions while the panel is closed
if (item.type === "mention" && !openRef.current) {
toast(item.title, { action: { label: "View", onClick: () => setOpen(true) } });
}
};
return () => source.close();
}, []);
return (
<NotificationCenter
notifications={items}
open={open}
onOpenChange={(next) => {
openRef.current = next;
setOpen(next);
}}
onRead={(ids) => setItems((prev) => prev.map((n) => (ids.includes(n.id) ? { ...n, read: true } : n)))}
onDismiss={(id) => setItems((prev) => prev.filter((n) => n.id !== id))}
linkComponent={Link}
/>
);
}

The duplicate check matters: a reconnecting stream can send the same event twice.

Can I use just the bell, or a full notifications page?

Yes to both. The free Notification Bell is a standalone react notification bell: an iOS-style count badge whose digits roll, a swing when the count goes up and a 44px hit area on touch. Use it with your own panel, or pass it to the center as its trigger with your unread count. For a /notifications page or a sidebar panel, NotificationList renders the same tabs, groups, rows and load more without the bell.

tsx
import { NotificationBell } from "@/components/ui/notification-bell";
import { NotificationList } from "@/components/ui/notification-center";
<NotificationCenter notifications={items} trigger={<NotificationBell count={unread} size={40} />} />
<NotificationList title="Notifications" notifications={items} className="rounded-3xl border border-border" />
Press plus a few times quickly: the bell swings harder and the badge rolls to the new number, then reads 99+ past the max
$ npx wingo-ui@latest add notification-bell
FreeNotification Bell docs

What are the common notification center pitfalls?

  • Toast-only events. If an event matters, it goes in the inbox too. Toasts time out.
  • Dropping the row before it collapses. Remove it in onDismiss, which runs after the animation, not in your own swipe handler.
  • Unstable ids. Reads and dismissals are tracked by id. Generate it on the server, never from the array index.
  • Announcing the first fetch. The component treats the first non-empty list as the baseline, so a page load does not read forty titles aloud. Keep that behavior if you build your own.

How do I add it to my project?

Run npx wingo-ui@latest add notification-center. The CLI copies the source into components/ui, adds the Wingo UI components, hooks and helpers it imports, and installs @radix-ui/react-popover, motion and lucide-react. The Notification Center is a Pro component (pricing); the Notification Bell and the Toast are free. More single-component deep dives are in the component guides category, and the React form components guide applies the same touch, keyboard and screen reader rules to every form control.

Components in this post

  • Notification Center

    A React notification center: a bell with an unread count that opens an inbox with tabs, day groups, inline actions and swipe to dismiss.

    Pro
  • Notification Bell

    A React notification bell with an iOS-style unread badge that rolls its digits on new alerts.

    Free
  • Toast

    React toast notifications: mount one Toaster and call toast() from anywhere, with stacking, swipe to dismiss and a pause while you read.

    Free

FAQ

What is the difference between a toast and a notification center?

A toast confirms something the user just did and disappears after a few seconds. A notification center keeps a record of what happened, including while the user was away, so they can read and act on it later. Send every important event to the inbox and only the urgent ones to a toast as well.

Should the bell count unread or unseen notifications?

Unread counts drop as each notification is opened or marked read; unseen counts reset as soon as the panel opens. Knock lets you pick either for its bell through badgeCountType. The Wingo UI Notification Center counts unread by default, and its count prop takes any number, such as an unseen count.

How do I get real-time notifications into a React inbox?

Open a WebSocket or a Server-Sent Events stream in an effect, prepend each new notification to your state and pass the array to the inbox. The component animates the new row, swings the bell and announces it to screen readers; your server decides what is sent.

Do I need Novu or Knock to build a notification center?

Only if you want a hosted service to deliver and store notifications across channels such as email, push and in-app. If your backend already stores notifications, a UI component that takes an array is enough, and you keep your own data model.

Is the Wingo UI Notification Center free?

The Notification Center is a Pro component. The standalone Notification Bell and the Toast are free.

  • Notifications
  • Inbox
  • Toast
  • Mobile UI
  • Accessibility
  • React

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu founded Wingo UI. He builds its component library, CLI and MCP server, and writes about React interfaces for phones and AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Comparisons and alternatives

Chakra UI vs shadcn in 2026: Style Props or Tailwind?

Chakra UI vs shadcn in October 2026: style props on Emotion or Tailwind source you own. Theming, Turbopack, Server Components and phones, with code.

SRSerban Rusu·Oct 16, 2026·10 min read
Comparisons and alternatives

MUI Alternatives for React and Tailwind in 2026

MUI alternatives checked in October 2026: Mantine, Chakra UI, Ant Design, React Aria, headless kits and Tailwind source libraries, plus Data Grid replacements.

SRSerban Rusu·Oct 13, 2026·10 min read
Next.js and Tailwind CSS

Prefers Reduced Motion React: Calm Motion, No Mismatch

Prefers reduced motion React guide: respect the OS setting in Next.js without a hydration mismatch, and replace springs with short fades that keep feedback.

SRSerban Rusu·Oct 13, 2026·8 min read
Newsletter

Get new posts by email

New guides, tutorials and comparisons from the Wingo UI blog, sent when they are published.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components and blocks. Copy the source, make it yours, and let your coding agent build with it.

Product

  • Components
  • Templates
  • Pricing
  • AI agents
  • Theme generator

Resources

  • Docs
  • Blog
  • Changelog
  • Changelog RSS
  • llms.txt

Legal

  • Terms
  • Privacy
  • Refunds
  • License
  • Imprint

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities

© 2026 Wingo UI

Wingo UI