# Accessible List Labels

Every channel and thread list row exposes a single, composed accessible name so a screen reader announces one coherent sentence per row instead of walking each nested element. This recipe shows how to reshape that name: adding your own segments, reordering them, and taking full control.

For the underlying model and API surface see the [Accessibility guide](/chat/docs/sdk/react/guides/accessibility/#customizing-accessible-names-for-list-rows).

## How the label is built

The name is assembled from named **parts**, joined by a separator, in a defined **order**. `ChannelListItemUI` and `ThreadListItemUI` accept an `accessibleLabelConfig` prop that lets you override any of this:

- `parts` is merged over the defaults, so a new key is _added_ and a same-named key _overrides_.
- `order` decides which parts render and in what sequence. A part not listed in `order` is never rendered, even if defined.
- `separator` joins non-empty parts (defaults to `'. '`).
- `build` bypasses everything and returns the whole string yourself.

The default channel row reading order is name, active, unread count, last message, attachments, link preview, delivery status, time. The default thread row order is channel name, active, unread count, parent message, reply count, last activity.

## Adding custom parts to a channel row

Supply your own `ChannelListItemUI` through [`ComponentContext`](/chat/docs/sdk/react/components/contexts/component-context/) and forward an `accessibleLabelConfig`. Below we announce `Pinned`, `Muted`, and `Frozen` state right after the channel name. Each part returns its segment string, or `undefined` to be omitted, so `Pinned` is only spoken when the channel is actually pinned.

```tsx
import {
  Channel,
  ChannelList,
  ChannelListItemUI as DefaultChannelListItemUI,
  Chat,
  DEFAULT_CHANNEL_LIST_ITEM_LABEL_ORDER,
  type ChannelListItemLabelConfig,
  type ChannelListItemUIProps,
} from "stream-chat-react";

const accessibleLabelConfig: ChannelListItemLabelConfig = {
  parts: {
    pinned: ({ channel, t }) =>
      channel.state.membership?.pinned_at ? t("Pinned") : undefined,
    muted: ({ channel, t }) =>
      channel.muteStatus().muted ? t("Muted") : undefined,
    frozen: ({ channel, t }) =>
      channel.data?.frozen ? t("Frozen") : undefined,
  },
  // Spread the defaults so you keep the built-in segments, then insert your own after `name`.
  order: [
    "name",
    "pinned",
    "muted",
    "frozen",
    ...DEFAULT_CHANNEL_LIST_ITEM_LABEL_ORDER.slice(1),
  ],
};

const CustomChannelPreview = (props: ChannelListItemUIProps) => (
  <DefaultChannelListItemUI
    {...props}
    accessibleLabelConfig={accessibleLabelConfig}
  />
);

export const App = ({ client }) => (
  <Chat client={client}>
    <ChannelList
      filters={{ type: "messaging", members: { $in: [client.userID] } }}
      Preview={CustomChannelPreview}
    />
    <Channel>{/* ... */}</Channel>
  </Chat>
);
```

A pinned, muted channel now reads roughly:

> General. Pinned. Muted. 3 unread messages. Last message from Alice: see you soon. 2 days ago

<Admonition type="note">

Parts call the translate function `t`, so provide your own i18n keys (or return plain strings). The SDK does not ship `Pinned`, `Muted`, or `Frozen` strings.

</Admonition>

## Reordering or dropping default parts

Because `order` is the single source of truth for what renders, you can trim a verbose label by listing only the parts you want. This announces just the name, unread count, and last message, dropping attachments, link previews, delivery status, and time:

```tsx
const accessibleLabelConfig: ChannelListItemLabelConfig = {
  order: ["name", "unreadCount", "lastMessage"],
};
```

## Customizing thread rows

`ThreadListItemUI` works the same way with `ThreadListItemLabelConfig` and `DEFAULT_THREAD_LIST_ITEM_LABEL_ORDER`. Here we add the participant count after the channel name:

```tsx
import {
  ThreadListItemUI as DefaultThreadListItemUI,
  DEFAULT_THREAD_LIST_ITEM_LABEL_ORDER,
  type ThreadListItemLabelConfig,
  type ThreadListItemUIProps,
} from "stream-chat-react";

const accessibleLabelConfig: ThreadListItemLabelConfig = {
  parts: {
    participants: ({ participantCount, t }) =>
      participantCount
        ? t("{{ count }} participants", { count: participantCount })
        : undefined,
  },
  order: [
    "name",
    "participants",
    ...DEFAULT_THREAD_LIST_ITEM_LABEL_ORDER.slice(1),
  ],
};

const CustomThreadListItem = (props: ThreadListItemUIProps) => (
  <DefaultThreadListItemUI
    {...props}
    accessibleLabelConfig={accessibleLabelConfig}
  />
);
// Provide { ThreadListItemUI: CustomThreadListItem } through ComponentContext.
```

## Taking full control

When part composition is not enough, set `build` to return the entire string. It receives the same row data and ignores `order`, `parts`, and `separator`:

```tsx
const accessibleLabelConfig: ChannelListItemLabelConfig = {
  build: ({ channel, displayTitle, unreadCount, t }) => {
    const name = displayTitle ?? channel.data?.name ?? t("Unnamed channel");
    return unreadCount
      ? t("{{ name }}, {{ count }} unread", { count: unreadCount, name })
      : name;
  },
};
```

Alternatively, write a fully custom `ChannelListItemUI` and call `composeChannelListItemAccessibleLabel` directly, importing `defaultChannelListItemLabelParts` to reuse the built-in segments.


---

This page was last updated at 2026-07-09T16:01:15.831Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/guides/customization/accessibility/accessible-list-labels/](https://getstream.io/chat/docs/sdk/react/guides/customization/accessibility/accessible-list-labels/).