# Snackbar

The SDK renders in-app notifications as a snackbar: a single floating card mounted by [`NotificationList`](/chat/docs/sdk/react-native/ui-components/in-app-notifications/notification-list/) and rendered by [`Notification`](/chat/docs/sdk/react-native/ui-components/in-app-notifications/notification/). This cookbook walks through customizing how that snackbar looks and behaves, from the lightest touch (theme tokens) to a full replacement (your own snackbar component on top of the SDK's notification store).

If you only need to know how to _emit_ notifications, see the [In-App Notifications cookbook](/chat/docs/sdk/react-native/guides/in-app-notifications/) instead.

## Best Practices

- Prefer theme tokens. The vast majority of brand-matching tweaks (colors, spacing, typography) need no overrides.
- Mount `NotificationList` once per active target. `MessageList`, `ChannelList`, and `ThreadList` already do this in the built-in layouts. Only mount your own when you've replaced those default child components.
- When overriding `Notification`, provide it through `WithComponents` and preserve the SDK's accessibility props: live-region for errors (`assertive`) vs other severities (`polite`), `accessibilityRole='alert'` for errors and `'summary'` otherwise, and a translatable `accessibilityLabel` on the close button.
- Don't mix custom and built-in snackbars at the same time. Pick one extension level and stick with it per app.
- If you need entirely different motion (e.g. top-of-screen banners instead of bottom snackbar), replace `NotificationList` rather than fighting the built-in animations.

## Level 1: Theme Tokens

The fastest customization is restyling. The theme exposes per-component slots for the snackbar:

```tsx
import { Chat, OverlayProvider } from "stream-chat-react-native";

const theme = {
  notification: {
    container: {
      backgroundColor: "#1f2937",
      borderRadius: 16,
      paddingHorizontal: 16,
      paddingVertical: 12,
    },
    message: {
      color: "#f9fafb",
      fontSize: 14,
    },
    actionButton: {
      backgroundColor: "#374151",
    },
    closeButton: {
      backgroundColor: "transparent",
    },
    iconContainer: {
      marginRight: 4,
    },
  },
  notificationList: {
    container: {
      // Bump the snackbar above a custom bottom toolbar
      bottom: 96,
    },
  },
};

export const App = () => (
  <OverlayProvider value={{ style: theme }}>
    <Chat client={client}>{/* ... */}</Chat>
  </OverlayProvider>
);
```

Available slots:

| Slot                            | Type        | What it styles                |
| ------------------------------- | ----------- | ----------------------------- |
| `notification.container`        | `ViewStyle` | The snackbar card             |
| `notification.contentContainer` | `ViewStyle` | Icon + message wrapper        |
| `notification.iconContainer`    | `ViewStyle` | Icon bubble                   |
| `notification.message`          | `TextStyle` | Message text                  |
| `notification.actionsContainer` | `ViewStyle` | Wrapper around action buttons |
| `notification.actionButton`     | `ViewStyle` | Each action button            |
| `notification.closeButton`      | `ViewStyle` | Dismiss button                |
| `notificationList.container`    | `ViewStyle` | The absolute-positioned host  |

## Level 2: Override the Icon

To change the severity-to-icon mapping without touching the rest of the snackbar, override `NotificationIcon` via `WithComponents` so every `Notification` instance picks it up:

```tsx
import { Chat, useTheme, WithComponents } from "stream-chat-react-native";
import type { NotificationIconProps } from "stream-chat-react-native";
import { CustomError, CustomCheck, CustomInfo, CustomWarning } from "./icons";

const IconBySeverity = {
  error: CustomError,
  success: CustomCheck,
  warning: CustomWarning,
  info: CustomInfo,
} as const;

const CustomNotificationIcon = ({ notification }: NotificationIconProps) => {
  const {
    theme: { semantics },
  } = useTheme();
  const severity = notification.severity;
  const Icon = severity
    ? IconBySeverity[severity as keyof typeof IconBySeverity]
    : null;

  if (!Icon) return null;

  return <Icon height={20} width={20} color={semantics.textOnInverse} />;
};

<WithComponents overrides={{ NotificationIcon: CustomNotificationIcon }}>
  <Chat client={client}>{/* ... */}</Chat>
</WithComponents>;
```

## Level 3: Override the `Notification` Component

To restructure the snackbar, for example with a different layout, custom close gesture, or brand-specific motion, replace `Notification`. The component receives the notification object plus these optional props:

| Prop             | Type                                     | Description                                                                                              |
| ---------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `notification`   | `Notification`                           | The notification to render. Always present.                                                              |
| `Icon`           | `ComponentType<NotificationIconProps>`   | The resolved icon component (defaults to the `NotificationIcon` provided through `WithComponents`).      |
| `onDismiss`      | `() => void`                             | Call this to dismiss the active notification. `NotificationList` removes it from the store.              |
| `showClose`      | `boolean`                                | Whether to render the close button. `NotificationList` sets this to `true` for persistent notifications. |
| `entryDirection` | `'bottom' \| 'left' \| 'right' \| 'top'` | The animation direction the host picked. Useful for stacked transforms.                                  |

```tsx
import { Text, View } from "react-native";
import { Pressable as GHPressable } from "react-native-gesture-handler";
import {
  Chat,
  useTheme,
  useTranslationContext,
  WithComponents,
} from "stream-chat-react-native";
import type { NotificationProps } from "stream-chat-react-native";

const CustomNotification = ({
  Icon,
  notification,
  onDismiss,
  showClose,
}: NotificationProps) => {
  const {
    theme: { semantics },
  } = useTheme();
  const { t } = useTranslationContext();

  const isError = notification.severity === "error";
  const closeVisible = showClose || !notification.duration;

  return (
    <View
      accessibilityLiveRegion={isError ? "assertive" : "polite"}
      accessibilityRole={isError ? "alert" : "summary"}
      style={{
        backgroundColor: semantics.backgroundCoreInverse,
        borderRadius: 16,
        flexDirection: "row",
        padding: 12,
      }}
    >
      {Icon ? <Icon notification={notification} /> : null}
      <Text style={{ color: semantics.textOnInverse, flex: 1, marginLeft: 8 }}>
        {t(notification.message)}
      </Text>
      {notification.actions?.map((action, i) => (
        <GHPressable
          accessibilityRole="button"
          key={`${action.label}-${i}`}
          onPress={action.handler}
          style={{ marginLeft: 8 }}
        >
          <Text style={{ color: semantics.accentPrimary }}>{action.label}</Text>
        </GHPressable>
      ))}
      {closeVisible ? (
        <GHPressable
          accessibilityLabel={t("a11y/Dismiss notification")}
          accessibilityRole="button"
          hitSlop={8}
          onPress={onDismiss}
          style={{ marginLeft: 8 }}
        >
          <Text style={{ color: semantics.textOnInverse }}>✕</Text>
        </GHPressable>
      ) : null}
    </View>
  );
};

<WithComponents overrides={{ Notification: CustomNotification }}>
  <Chat client={client}>{/* ... */}</Chat>
</WithComponents>;
```

Two accessibility concerns to preserve when overriding:

- **Live region**: errors should be `assertive` (interrupts whatever the screen reader is reading) and other severities should be `polite` (queued).
- **Dismiss label**: pull the label from the translation key `a11y/Dismiss notification` rather than hardcoding "Close". The SDK ships translations for 12 locales.

The built-in component translates recognized notification `type` values before rendering. If you replace it, translate `notification.message` with `t(notification.message)` and add any type-specific message handling your app needs.

## Level 4: Replace `NotificationList`

Override `NotificationList` when you need a different presentation entirely, such as a multi-snackbar stack, a banner at the top of the screen, or a platform-native toast that bypasses React Native rendering. Use [`useNotificationListController`](/chat/docs/sdk/react-native/ui-components/in-app-notifications/hooks/use-notification-list-controller/) if you only need to swap the visual shell while keeping the SDK's selection, timeout, and target-routing logic:

```tsx
import Animated, { SlideInDown, SlideOutDown } from "react-native-reanimated";
import {
  Chat,
  useComponentsContext,
  useNotificationListController,
  WithComponents,
} from "stream-chat-react-native";
import type { NotificationListProps } from "stream-chat-react-native";

const TopBannerNotificationList = ({
  filter,
  hostId,
  panel,
}: NotificationListProps) => {
  const { Notification } = useComponentsContext();
  const { dismissNotification, notification } = useNotificationListController({
    filter,
    hostId,
    panel,
  });

  if (!notification) return null;

  return (
    <Animated.View
      entering={SlideInDown.duration(220)}
      exiting={SlideOutDown.duration(180)}
      style={{ left: 16, position: "absolute", right: 16, top: 16, zIndex: 20 }}
    >
      <Notification
        notification={notification}
        onDismiss={dismissNotification}
        showClose={!notification.duration}
      />
    </Animated.View>
  );
};

<WithComponents overrides={{ NotificationList: TopBannerNotificationList }}>
  <Chat client={client}>{/* ... */}</Chat>
</WithComponents>;
```

If you want a stacked snackbar (multiple notifications visible at once), drive the list off `useNotifications` directly. You're responsible for picking which notifications to show, starting their timeouts, and removing them on dismiss:

```tsx
import { useEffect } from "react";
import { Pressable, Text, View } from "react-native";
import {
  useNotificationApi,
  useNotifications,
  useNotificationTargetContext,
} from "stream-chat-react-native";

const StackedNotificationList = () => {
  const target = useNotificationTargetContext();
  const notifications = useNotifications({
    filter: (n) => !n.tags?.includes("system"),
    requireTarget: true,
    target,
  });
  const { removeNotification, startNotificationTimeout } = useNotificationApi();

  useEffect(() => {
    notifications.forEach((n) => {
      if (!n.duration) return;
      startNotificationTimeout(n.id);
    });
  }, [notifications, startNotificationTimeout]);

  return (
    <View
      style={{
        bottom: 16,
        left: 16,
        position: "absolute",
        right: 16,
        zIndex: 20,
      }}
    >
      {notifications.slice(-3).map((n) => (
        <Pressable
          accessibilityLabel={n.message}
          accessibilityLiveRegion={
            n.severity === "error" ? "assertive" : "polite"
          }
          accessibilityRole={n.severity === "error" ? "alert" : "summary"}
          key={n.id}
          onPress={() => removeNotification(n.id)}
          style={{
            backgroundColor: "#111827",
            borderRadius: 12,
            marginTop: 8,
            padding: 12,
          }}
        >
          <Text style={{ color: "#f9fafb" }}>{n.message}</Text>
        </Pressable>
      ))}
    </View>
  );
};
```

## Animations

`NotificationList` accepts an `enterFrom` prop (`'bottom' | 'left' | 'right' | 'top'`, default `'bottom'`) that picks one of the SDK's bounded-zoom transitions. The same value flows into `Notification.entryDirection`, so a custom snackbar can mirror the host's direction.

A single notification can also override the entry direction by setting `metadata.entryDirection` or `origin.context.entryDirection` when emitted:

```tsx
addNotification({
  message: "Pinned to top",
  origin: { emitter: "ChannelListItem", context: { entryDirection: "top" } },
  options: { severity: "success" },
});
```

For fully custom motion, replace `NotificationList` and use any Reanimated transition you like.

## Mounting Placement

`MessageList`, `ChannelList`, and `ThreadList` mount a `NotificationList` for you in the built-in layouts. `Channel` and `Thread` provide the target context around their message lists. If you've replaced one of those defaults, mount the snackbar yourself inside the same target context:

```tsx
<Channel channel={channel}>
  <CustomMessageList />
  {/* CustomMessageList doesn't render NotificationList, so mount it here */}
  <NotificationList panel="channel" />
</Channel>
```

`NotificationList` also accepts `bottomOffset` and `topOffset` props for pushing the snackbar above a floating composer or below a sticky header. The default `MessageList` already wires `bottomOffset` to the floating message-input height.

## Accessibility

The built-in `Notification` ships with the right accessibility semantics out of the box:

- Live region: `assertive` for `severity: 'error'`, `polite` for everything else.
- Role: `alert` for errors, `summary` for other severities.
- The dismiss button has a translatable `accessibilityLabel` from the `a11y/Dismiss notification` translation key.
- `NotificationList` is labelled with the `a11y/Notifications` key.
- Action buttons have `accessibilityRole='button'`.

When overriding `Notification` or `NotificationList`, preserve these. Adopt the same live-region rules and use the existing translation keys rather than hardcoding strings. The SDK already translates them across 12 locales.

For animations, respect the user's reduced-motion preference. The SDK's `useReducedMotionPreference` hook returns `true` when the OS-level setting is enabled. In your custom snackbar, fall back to instant transitions:

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

const reduceMotion = useReducedMotionPreference();
const duration = reduceMotion ? 0 : 200;
```

Mirror these patterns when shipping a custom snackbar so screen reader and reduced-motion users get the same experience as the rest of the app.


---

This page was last updated at 2026-05-14T07:09:01.258Z.

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