# Notifications UI

The notifications UI consists of the components that show notifications: one `<NotificationList/>` for each part of the screen (panel) rendering `<Notification/>` for each registered notification in `client.notifications`. Use this page to know what users see: what a notification looks like, which part of the screen shows it, and in which order a list shows its notifications. It also covers how to change where they show and how they look. Who publishes notifications and what they contain is on [Publishing notifications](https://getstream.io/chat/docs/sdk/react/state/publishing-notifications/). The props of `<NotificationList/>`, `<Notification/>` and `<NotificationAnnouncer/>` are in the [Notifications reference](https://getstream.io/chat/docs/sdk/react/component-reference/utilities/notifications/).

![A persistent error notification at the bottom of a channel, with its parts numbered: the severity icon, the message "Message failed to send", a Retry button and a close button](https://getstream.io/docs-assets/images/d1638b8fa0f7.png)

This is a persistent error notification with an action, at the bottom of a channel. Its parts:

| Part             | What it shows                                                                                                                              | Explained in                                                                                                           |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| 1. Severity icon | An icon per severity: success, error, warning and loading. An info notification has none.                                                  | [Severity, actions and durations](https://getstream.io/chat/docs/sdk/react/state/publishing-notifications/#severity-actions-and-durations) |
| 2. Message       | The notification's text, translated. A message wider than the panel wraps, unless the notification has actions, which keep it on one line. | [Notifications text](https://getstream.io/chat/docs/sdk/react/features/notifications/notifications-text/)                                  |
| 3. Action button | One button per action the notification carries, such as "Retry"                                                                            | [Severity, actions and durations](https://getstream.io/chat/docs/sdk/react/state/publishing-notifications/#severity-actions-and-durations) |
| 4. Close button  | Shown on a persistent notification, one that stays until it's closed                                                                       | [Notification lists and panels](#notification-lists-and-panels)                                                        |

Notifications look different by severity, by whether they stay, and by their actions:

![A success notification, Message pinned, with a checkmark icon. It disappears after its duration.](https://getstream.io/docs-assets/images/61a2d86f260a.png)

![An error notification, Error adding flag, with an exclamation icon](https://getstream.io/docs-assets/images/5ec664a381ba.png)

![A warning notification, You are offline. Messages will send when you reconnect., with a warning icon](https://getstream.io/docs-assets/images/881c7e03db52.png)

![An info notification, Bob joined the channel, with no icon](https://getstream.io/docs-assets/images/61ad0779017d.png)

![A loading notification, Uploading header-layout.pdf…, with a refresh icon](https://getstream.io/docs-assets/images/886a463f6e21.png)

![A persistent info notification, Slow mode is on: one message every 30 seconds, with a close button](https://getstream.io/docs-assets/images/6b1d762e3a93.png)

![An error notification, Message failed to send, with two actions, Retry and Delete](https://getstream.io/docs-assets/images/c3b7c8ec9dcb.png)

>
> **Info:** **Example**
>
> The user flags a message in a thread:
>
> 1. "Message has been successfully flagged" slides in at the bottom of the thread, with a success icon.
> 2. Three seconds after it appears, it slides out.
> 3. A screen reader reads "Success notification: Message has been successfully flagged".
>
> If the flag fails, an error notification appears in the same place: the server's error message, or "Error adding flag" when there is none. A screen reader reads it at once, interrupting what it was saying.
>

## Notification lists and panels

A panel is a part of the screen with its own notification list. The SDK renders one list per panel:

| Panel          | Rendered by                                                            |
| -------------- | ---------------------------------------------------------------------- |
| `channel`      | `<MessageList/>` and `<VirtualizedMessageList/>` inside a `<Channel/>` |
| `thread`       | `<MessageList/>` and `<VirtualizedMessageList/>` inside a `<Thread/>`  |
| `channel-list` | `<ChannelNavigation/>`, below its channel lists                        |
| `thread-list`  | `<ThreadList/>`                                                        |
| `modal`        | each open modal (`<GlobalModal/>`)                                     |

Each panel's list shows its notifications at the bottom of that panel:

![The channel list column, with the header, search and six channels, and a success notification, Channel archived, at its bottom](https://getstream.io/docs-assets/images/0682e314924b.png)

![An open thread, with its header, parent message and replies, and an error notification, Reply failed to send, just above its composer](https://getstream.io/docs-assets/images/8e814e3c6663.png)

A list shows one notification at a time and queues the rest:

- **A persistent notification goes first.** A notification that stays, usually one published with `duration: 0` because it carries an action such as "Retry", jumps the queue. It stays until it's dismissed or a newer persistent one arrives. [Severity, actions and durations](https://getstream.io/chat/docs/sdk/react/state/publishing-notifications/#severity-actions-and-durations) lists when a notification stays.
- **A repeat replaces its original.** When a notification of the same `type` arrives while one with a duration is showing, the new one takes its place at once.
- **Otherwise the oldest goes next.** A notification stays on screen for at least `minDisplayMs` (1000 ms by default) before a queued one replaces it.
- **The countdown starts when it's seen.** A notification's auto-dismiss timer starts when it becomes visible in a list, so one waiting in a queue doesn't expire unseen.
- **It stays three seconds by default.** A notification published with `duration: 0` stays until the user closes it, and shows a close button.

To change which queued notification shows next, pass `<NotificationList/>` a `pickNext` function. Factory `createDefaultPickNext(pickNewest)` exported by the SDK, builds one that keeps these queue rules but shows the newest queued notification instead of the oldest. [Replacing the list or a notification](#replacing-the-list-or-a-notification) uses it.

## Where a notification shows

### Publishing a notification

Publish with `addNotification` from `useNotificationApi()` in a child component of `<Chat/>`. Here is a `CopyLinkButton` component that confirms a copied link:

```tsx
import { useNotificationApi } from "stream-chat-react";

export const CopyLinkButton = ({ link }: { link: string }) => {
  const { addNotification } = useNotificationApi();

  const copy = async () => {
    await navigator.clipboard.writeText(link);
    addNotification({
      emitter: "CopyLinkButton",
      message: "Link copied",
      severity: "success",
      type: "app:link:copy:success",
    });
  };

  return (
    <button onClick={copy} type="button">
      Copy link
    </button>
  );
};
```

Outside `<Chat/>`, publish with `client.notifications.add()`, as in [Publishing a notification](https://getstream.io/chat/docs/sdk/react/state/publishing-notifications/#publishing-a-notification).

### Where it appears by default

`addNotification` shows the notification in the parent panel of the publishing component. It takes the first row that applies:

| The publishing component                                                           | The notification shows in |
| ---------------------------------------------------------------------------------- | ------------------------- |
| Is inside a `<Thread/>`                                                            | The thread                |
| Is inside a `<Channel/>`                                                           | The channel               |
| Is anywhere else while `<ChatView/>` shows its threads view                        | The thread list           |
| Is inside `<ChannelLists/>`. A `<ChannelList/>` rendered on its own doesn't count. | The channel list          |
| Is anywhere else                                                                   | The channel               |

While a modal is open, the notification shows in the modal instead of behind it.

### Showing it somewhere else

Pass `targetPanels` to `addNotification`. For example, `targetPanels: ["channel-list"]` shows the notification in the channel list, independent of where the component that publishes it sits.

The SDK's lists publish their query errors this way. `<ChannelList/>` targets the `channel-list` panel, and `<ThreadList/>` the `thread-list` panel. A `<ChannelList/>` rendered without `<ChannelNavigation/>` renders no `<NotificationList/>` for the `channel-list` panel, so its query errors don't show, unless you render a `<NotificationList panel="channel-list" />` yourself. [When the first page fails](https://getstream.io/chat/docs/sdk/react/ui/components/list-states-ui/#when-the-first-page-fails) explains what the list shows instead.

### Notifications from stream-chat

A notification that `stream-chat` publishes, such as a failed upload, follows the composer it came from. Publishing from a thread's composer targets the thread panel, a channel's targets the channel panel. Any other shows in the channel.

### How it works

`addNotification` tags a notification with `target:<panel>` for each panel it should show in, and each list shows the notifications tagged for its own panel. A notification without a panel tag goes to the list's `fallbackPanel`, which is `channel` by default.

## Hiding notifications in lists

To keep some notifications out of the lists, pass `<Chat/>` a `notificationDisplayFilter`. It gets each notification and the `panel` of the list about to show it, and returns `false` to hide it there. Here is an `App` component whose channel list shows no success confirmations:

```tsx
import { Chat } from "stream-chat-react";
import type { NotificationDisplayFilter } from "stream-chat-react";
import type { PropsWithChildren } from "react";

// Keep success confirmations out of the channel list. Every other notification shows.
const notificationDisplayFilter: NotificationDisplayFilter = ({
  notification,
  panel,
}) => !(panel === "channel-list" && notification.severity === "success");

export const App = ({ children }: PropsWithChildren) => (
  <Chat client={client} notificationDisplayFilter={notificationDisplayFilter}>
    {children}
  </Chat>
);
```

>
> **Warning:** A filter you pass replaces the default one, which handles modals:
>
> - While a modal is open, notifications show only in the modal's list. Notifications for other panels are held back until the modal closes.
> - A notification tagged for a modal that has already closed shows in its other panel instead, so a confirmation sent as a dialog closes isn't lost.
>
> The default filter isn't exported, so your filter can't add to it: with your own filter, notifications no longer wait for an open modal to close.
>

### Where the filter applies

Where you put a filter decides how far it reaches. The examples hide success notifications.

#### Everywhere

Give the filter to `<Chat/>` as `notificationDisplayFilter`, as in the `App` component in [Hiding notifications in lists](#hiding-notifications-in-lists). Every list in the app checks it.

#### In one area of the app

`<NotificationConfigurationProvider/>` holds the display filter for the components inside it. `<Chat/>` renders one with its `notificationDisplayFilter`, and every list reads the filter from the nearest one. Render your own around an area, such as a support section, and the lists inside it use its `displayFilter` instead of the one from `<Chat/>`. Like a filter on `<Chat/>`, it replaces the default modal handling for those lists:

```tsx
import type { PropsWithChildren } from "react";
import { NotificationConfigurationProvider } from "stream-chat-react";
import type { NotificationDisplayFilter } from "stream-chat-react";

const hideSuccess: NotificationDisplayFilter = ({ notification }) =>
  notification.severity !== "success";

// Lists inside this section show no success notifications. The rest of the app keeps the filter from Chat.
export const SupportSection = ({ children }: PropsWithChildren) => (
  <NotificationConfigurationProvider displayFilter={hideSuccess}>
    {children}
  </NotificationConfigurationProvider>
);
```

#### In one list

The SDK renders its lists itself, so give one of them a `filter` by replacing the `NotificationList` key with a component that adds the filter for that list's panel. The filter runs after the display filter. Declare it outside the component: a new function on every render resubscribes the list.

```tsx
import type { PropsWithChildren } from "react";
import type { Notification } from "stream-chat";
import { NotificationList, WithComponents } from "stream-chat-react";
import type { NotificationListProps } from "stream-chat-react";

const isNotSuccess = (notification: Notification) =>
  notification.severity !== "success";

// The channel list's list hides success notifications. Every other list stays as it is.
const FilteredNotificationList = (props: NotificationListProps) => (
  <NotificationList
    {...props}
    filter={props.panel === "channel-list" ? isNotSuccess : props.filter}
  />
);

const overrides = { NotificationList: FilteredNotificationList };

export const WithQuietChannelList = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);
```

#### In a list of your own

`useNotifications` returns the notifications for a panel, and you render them as you like. It ignores the display filter unless you pass `applyDisplayFilter: true`, and it returns system notifications too, so leave those out. A notification you show this way only disappears on its own once you start its countdown with `ensureNotificationTimeout(id)`, as `<NotificationList/>` does when a notification becomes visible.

Here is a `ChannelStatusLine` component, rendered inside a `<Channel/>`, that shows the channel's latest notification as a line of text:

```tsx
import { useEffect } from "react";
import type { Notification } from "stream-chat";
import {
  hasSystemNotificationTag,
  useNotificationApi,
  useNotifications,
} from "stream-chat-react";

const isNotSystem = (notification: Notification) =>
  !hasSystemNotificationTag(notification);

export const ChannelStatusLine = () => {
  const notifications = useNotifications({
    applyDisplayFilter: true,
    filter: isNotSystem,
    panel: "channel",
  });
  const latest = notifications[notifications.length - 1];
  const { ensureNotificationTimeout } = useNotificationApi();

  // Starts the countdown once the notification is on screen.
  useEffect(() => {
    if (latest) ensureNotificationTimeout(latest.id);
  }, [latest, ensureNotificationTimeout]);

  if (!latest) return null;
  return <p className="channel-status-line">{latest.message}</p>;
};
```

The channel's own notification list, which the SDK renders in the message list, still shows the same notifications. To show them only in yours, hide them from the SDK's list with a filter, as in [In one list](#in-one-list).

### Hiding all of them

Return `false` for every notification. Screen readers still hear them: [`<NotificationAnnouncer/>`](https://getstream.io/chat/docs/sdk/react/features/notifications/accessibility/#notification-announcements) reads every notification, shown or not. To silence it, replace it with a component that renders nothing. `<Chat/>` reads it from the components above it, so `<WithComponents/>` wraps `<Chat/>`:

```tsx
import type { PropsWithChildren } from "react";
import { Chat, WithComponents } from "stream-chat-react";
import type { NotificationDisplayFilter } from "stream-chat-react";

// No list shows a notification.
const hideAllNotifications: NotificationDisplayFilter = () => false;

// Screen readers hear none either.
const SilentAnnouncer = () => null;
const overrides = { NotificationAnnouncer: SilentAnnouncer };

export const App = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>
    <Chat client={client} notificationDisplayFilter={hideAllNotifications}>
      {children}
    </Chat>
  </WithComponents>
);
```

## Customizing

### Replacing the list or a notification

Both are keys in `ComponentContext`. `NotificationList` replaces every list, the modal's included, and `Notification` replaces how each notification renders:

```tsx
import { forwardRef } from "react";
import type { PropsWithChildren } from "react";
import {
  createDefaultPickNext,
  Notification,
  NotificationList,
  pickNewest,
  WithComponents,
} from "stream-chat-react";
import type {
  NotificationListProps,
  NotificationProps,
} from "stream-chat-react";

const pickNewestFirst = createDefaultPickNext(pickNewest);

// Every list slides in from the top and shows the newest queued notification first.
const TopNotificationList = (props: NotificationListProps) => (
  <NotificationList
    {...props}
    enterFrom="top"
    pickNext={pickNewestFirst}
    verticalAlignment="top"
  />
);

// The list starts a notification's countdown once its element is visible,
// so the component forwards its ref.
const BorderedNotification = forwardRef<HTMLDivElement, NotificationProps>(
  (props, ref) => (
    <Notification {...props} className="bordered-notification" ref={ref} />
  ),
);

const notificationOverrides = {
  Notification: BorderedNotification,
  NotificationList: TopNotificationList,
};

export const WithTopNotifications = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={notificationOverrides}>{children}</WithComponents>
);
```

A replacement receives the props `<NotificationList/>` passes to `<Notification/>`, the animation props included, and should pass them on, as `BorderedNotification` does. The props are in the [Notifications reference](https://getstream.io/chat/docs/sdk/react/component-reference/utilities/notifications/#notification).

To render notifications your own way, see [In a list of your own](#in-a-list-of-your-own).

### Styling

Three CSS variables style a notification: `--str-chat__notification-background`, `--str-chat__notification-color` and `--str-chat__notification-border-radius`. Each notification has the class `str-chat__notification--<severity>`, for example `str-chat__notification--error`, and each list the class `str-chat__notification-list--<panel>`:

```css
.str-chat__notification--error {
  --str-chat__notification-background: #b91c1c;
  --str-chat__notification-color: #fff;
}

.str-chat__notification-list--channel-list .str-chat__notification {
  max-width: 250px;
}
```

## System notifications

A system notification is about the whole app, not one part of the screen. It carries the `system` tag and no `target:` tags, so no `<NotificationList/>` shows it. [`<NotificationAnnouncer/>`](https://getstream.io/chat/docs/sdk/react/features/notifications/accessibility/#notification-announcements) still reads it to screen readers.

`<Chat/>` publishes one while the client can't reach Stream:

| Text                   | When                                                                                                                                      |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| "Waiting for network…" | As soon as the device reports it has no network                                                                                           |
| "Reconnecting…"        | When the socket has been down for `client.wsConnection.config.offlineNotificationDisplayDelayMs`. A drop shorter than that shows nothing. |

Both have the type `system:network:connection:lost` and the severity `loading`, stay until the connection is back, and then disappear. The text is already translated: `<Chat/>` publishes it with `t()`.

### A banner for system notifications

The SDK exports no banner component for system notifications, so without one of your own users see nothing on screen. Build it with `useSystemNotifications`, which returns the system notifications in `client.notifications`, and render it inside `<Chat/>`.

Here is an `App` component with a `SystemNotificationBanner` across the top of the app:

```tsx
import type { PropsWithChildren } from "react";
import { Chat, useSystemNotifications } from "stream-chat-react";

const SystemNotificationBanner = () => {
  const [notification] = useSystemNotifications();
  if (!notification) return null;

  return <div className="system-banner">{notification.message}</div>;
};

// Inside Chat, above the rest of the app.
export const App = ({ children }: PropsWithChildren) => (
  <Chat client={client}>
    <SystemNotificationBanner />
    {children}
  </Chat>
);
```

![The app with a bar across the top reading Waiting for network…: the lost-connection system notification, shown in a banner the app renders](https://getstream.io/docs-assets/images/85611aeccbde.png)

The banner needs no `role="status"` or other live region: `<NotificationAnnouncer/>` already reads system notifications, so a live region would announce them twice. The [System notification banner](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/notifications/system-notification-banner/) recipe builds a complete banner, with an icon per severity, action buttons, styles and an animation.

### Choosing which ones show

The display filter doesn't affect system notifications: the banner shows what `useSystemNotifications` returns. To leave some out, pass the hook a memoized `filter` function (so the subscription stays stable). Here is a `SystemNotificationBanner` component for an app with a connection indicator of its own. It shows every system notification except the lost connection:

```tsx
import { useCallback } from "react";
import type { Notification } from "stream-chat";
import { useSystemNotifications } from "stream-chat-react";

export const SystemNotificationBanner = () => {
  // Memoized, so the subscription stays the same across renders.
  const isNotConnectionLost = useCallback(
    (notification: Notification) =>
      notification.type !== "system:network:connection:lost",
    [],
  );
  const [notification] = useSystemNotifications({
    filter: isNotConnectionLost,
  });
  if (!notification) return null;

  return <div className="system-banner">{notification.message}</div>;
};
```

Render it inside `<Chat/>`, as in [A banner for system notifications](#a-banner-for-system-notifications).

To show no system notifications, render no banner. To keep screen readers quiet about them too, replace the announcer, as in [Hiding all of them](#hiding-all-of-them).

### Publishing a system notification

Publish one of your own with `addSystemNotification` from `useNotificationApi()`. It returns the notification's id: remove it with `removeNotification` when the condition is over. Here is a `MaintenanceNotice` component that shows a notice while it's mounted:

```tsx
import { useEffect } from "react";
import { useNotificationApi } from "stream-chat-react";

export const MaintenanceNotice = () => {
  const { addSystemNotification, removeNotification } = useNotificationApi();

  useEffect(() => {
    const id = addSystemNotification({
      // Stays until it's removed.
      duration: 0,
      emitter: "MaintenanceNotice",
      message: "Maintenance in 10 minutes",
      severity: "warning",
    });
    return () => removeNotification(id);
  }, [addSystemNotification, removeNotification]);

  return null;
};
```

![The app with a bar across the top reading Maintenance in 10 minutes, with a warning icon: a system notification the app published, in the same banner](https://getstream.io/docs-assets/images/55c268afe128.png)

## Used in

- [Notifications text](https://getstream.io/chat/docs/sdk/react/features/notifications/notifications-text/) and [Notifications accessibility](https://getstream.io/chat/docs/sdk/react/features/notifications/accessibility/)
- [Connection status](https://getstream.io/chat/docs/sdk/react/features/app-experience/connection-status/)
- [Localization](https://getstream.io/chat/docs/sdk/react/features/app-experience/localization/)
- [Message actions](https://getstream.io/chat/docs/sdk/react/features/messaging/message-actions/)
- [Reminders and save for later](https://getstream.io/chat/docs/sdk/react/features/messaging/reminders-and-save-for-later/)
- [System notification banner](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/notifications/system-notification-banner/), [Custom snackbars](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/notifications/custom-snackbars/) and [Starting a conversation](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/channel-list/starting-a-conversation/)

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/ui/components/notifications-ui/](https://getstream.io/chat/docs/sdk/react/ui/components/notifications-ui/).