# Notifications accessibility

Notifications reach users of screen readers without them looking at the screen. Use this page to know what VoiceOver and TalkBack announce for a notification, and how to have more of them read. Accessibility across the SDK is on [Accessibility](https://getstream.io/chat/docs/sdk/react-native/features/app-experience/accessibility/).

## Notification announcements

Notifications are the messages the SDK and your code publish to `client.notifications`, for example with `addNotification` from `useNotificationApi()`. [Displaying notifications](https://getstream.io/chat/docs/sdk/react-native/ui/components/notifications-ui/) shows where they appear and how they look.

The default `Notification` carries its own accessibility props:

- **Android.** It is a live region, `assertive` for an error and `polite` for every other severity, so TalkBack reads it when it appears. This doesn't depend on `accessibility.enabled` in `OverlayProvider`.
- **iOS.** VoiceOver has no live regions, so nothing is read when a notification appears. A user who moves focus to its text hears it.
- **Role.** An error has the `alert` role and every other severity the `summary` role.
- **Close button.** A notification without a duration, or one shown with `showClose`, has a close button labeled "Dismiss notification".
- **Action buttons.** Each action is a button, labeled with the action's `label`.

The SDK's `NotificationAnnouncer`, which `Channel` and `Thread` render, announces the connection going down and coming back, and not notifications. [Connection announcements](https://getstream.io/chat/docs/sdk/react-native/features/app-experience/accessibility/#connection-announcements) covers it.

A system notification is shown by no list, so neither platform reads it unless the banner your app renders for it is a live region of its own, or your app announces it, as [Announcing notifications yourself](#announcing-notifications-yourself) shows.

### Time to hear and act

A notification leaves after its duration, which is three seconds by default for a notification with a severity, and at least five when it has action buttons. A screen-reader user may not reach an action button in that time. Publish a notification with an action using `duration: 0`, so it stays until the user acts or dismisses it, and the list shows a close button. The SDK does this for the notification that asks for microphone permission:

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

export const useNotifyRetry = (retry: () => void) => {
  const { addNotification } = useNotificationApi();

  return () =>
    addNotification({
      message: "Could not save your changes",
      options: {
        actions: [{ handler: retry, label: "Try again" }],
        duration: 0,
        severity: "error",
        type: "app:changes:save:failed",
      },
      origin: { emitter: "useNotifyRetry" },
    });
};
```

[Publishing notifications](https://getstream.io/chat/docs/sdk/react-native/state/publishing-notifications/#severity-actions-and-durations) explains durations.

## Announcing notifications yourself

To have notifications read on iOS, or to read the ones no list shows, announce them with `useAccessibilityAnnouncer()`. It calls `AccessibilityInfo.announceForAccessibility()` on both platforms, and does nothing unless `OverlayProvider` has `accessibility={{ enabled: true }}`.

This component reads every new notification. On Android it leaves out the ones a list shows, which the live region already reads, and keeps the system ones:

```tsx
import { useEffect, useRef } from "react";
import { Platform } from "react-native";
import type { Notification } from "stream-chat";
import {
  hasSystemNotificationTag,
  useAccessibilityAnnouncer,
  useNotifications,
  useTranslationContext,
} from "stream-chat-react-native";

export const NotificationScreenReaderAnnouncer = () => {
  const announce = useAccessibilityAnnouncer();
  const { t } = useTranslationContext();
  const notifications = useNotifications();
  const seenIdsRef = useRef<Set<string> | null>(null);

  useEffect(() => {
    // The notifications already in the store when this mounts aren't read.
    if (!seenIdsRef.current) {
      seenIdsRef.current = new Set(notifications.map(({ id }) => id));
      return;
    }

    const seenIds = seenIdsRef.current;
    notifications.forEach((notification: Notification) => {
      if (seenIds.has(notification.id)) return;
      seenIds.add(notification.id);

      if (Platform.OS === "android" && !hasSystemNotificationTag(notification))
        return;

      // The message of a notification stream-chat publishes is English.
      const message =
        notification.type === "api:attachment:upload:failed"
          ? t("notifications.attachmentUploadFailed.error")
          : notification.message;

      announce(
        message,
        notification.severity === "error" ? "assertive" : "polite",
      );
    });
  }, [announce, notifications, t]);

  return null;
};
```

Render it once, inside `Chat`, for example next to your navigator. Two copies read every notification twice.

The text of a notification is its `message`. The SDK's components translate it before publishing, and so does your code, as on [Notifications text](https://getstream.io/chat/docs/sdk/react-native/features/notifications/notifications-text/#text-of-notifications-you-publish). The notifications `stream-chat` publishes carry untranslated English, which the `Notification` component replaces with a translation key as it renders, by the notification's `type`. The announcer above does the same for one type, and a complete one maps the types your app can see, with the keys listed in [Keys by type](https://getstream.io/chat/docs/sdk/react-native/features/notifications/notifications-text/#keys-by-type).

`useNotifications()` returns every notification in the store, system ones included. Pass it a `filter`, declared at module scope, to read fewer.

## Localizing notification accessibility

The accessible names are translation keys like the rest of the UI:

- `notifications.dismiss.accessibilityLabel` is the close button, "Dismiss notification".
- `notifications.list.accessibilityLabel` is the label of the list container, "Notifications".
- `a11y.connection.offline.accessibilityLabel`, `a11y.connection.reconnecting.accessibilityLabel` and `a11y.connection.connected.accessibilityLabel` are the connection announcements.

[Localizing announcements](https://getstream.io/chat/docs/sdk/react-native/features/app-experience/accessibility/#localizing-announcements) explains how announcements are translated.

## Used in

- [Displaying notifications](https://getstream.io/chat/docs/sdk/react-native/ui/components/notifications-ui/)
- [Accessibility](https://getstream.io/chat/docs/sdk/react-native/features/app-experience/accessibility/)

---

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