This is beta documentation for Stream Chat React SDK v14. For the latest stable version, see the latest version (v13) .

Channel List UI

ChannelList is the primary navigation surface in most chat apps. Even when you want a heavily customized list, it is usually better to build on the SDK list than to reimplement channel events, unread counts, and active-channel wiring yourself.

Best Practices

  • Start from ChannelList Preview={...} before reaching for renderChannels.
  • Preserve the row click behavior so the active channel stays in sync with Channel.
  • Prefer displayTitle, displayImage, and latestMessagePreview from preview props over recomputing everything manually.
  • If you build preview text from useLatestMessagePreview(), handle native giphy messages separately from ordinary images.
  • Use aria-pressed for the selected row state to match the default preview semantics.
  • Keep preview rows lightweight and CSS-driven.

Custom Channel Preview

The simplest customization point is the Preview prop:

import { ChannelList } from "stream-chat-react";

<ChannelList Preview={CustomChannelPreview} />;

Here is a minimal custom row that uses the current preview helpers:

import {
  ChannelAvatar,
  ChannelList,
  ChannelPreviewTimestamp,
} from "stream-chat-react";

const CustomChannelPreview = ({
  active,
  channel,
  displayImage,
  displayTitle,
  latestMessagePreview,
  onSelect,
  setActiveChannel,
}) => (
  <button
    aria-pressed={active}
    className="channel-preview"
    onClick={(event) => {
      if (onSelect) {
        onSelect(event);
      } else {
        setActiveChannel?.(channel);
      }
    }}
  >
    <ChannelAvatar imageUrl={displayImage} size="xl" userName={displayTitle} />
    <div className="channel-preview__main">
      <div className="channel-preview__header">
        <span>{displayTitle}</span>
        <ChannelPreviewTimestamp
          lastMessage={channel.state.latestMessages.at(-1)}
        />
      </div>
      <div className="channel-preview__message">{latestMessagePreview}</div>
    </div>
  </button>
);
.channel-preview {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  padding: 12px;
  border: 0;
  background: none;
  text-align: left;
}

.channel-preview__main {
  flex: 1;
  min-width: 0;
}

.channel-preview__header {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-weight: 600;
}

.channel-preview__message {
  color: #6b7280;
}

Using Display Helpers

If you need to derive the title or image yourself, use the current helper utilities instead of older channel-instance helpers:

import {
  ChannelAvatar,
  getChannelDisplayImage,
  useChannelDisplayName,
} from "stream-chat-react";

const CustomChannelPreview = ({ channel, latestMessagePreview }) => {
  const displayTitle = useChannelDisplayName(channel);
  const displayImage = getChannelDisplayImage(channel);

  return (
    <div>
      <ChannelAvatar
        imageUrl={displayImage}
        size="xl"
        userName={displayTitle}
      />
      <div>{latestMessagePreview}</div>
    </div>
  );
};

useChannelDisplayName() and getChannelDisplayImage() keep direct-message and group-channel fallbacks aligned with the default SDK preview behavior.

If you build your own summary text with useLatestMessagePreview(), include a giphy branch. Native giphy attachments now resolve to their own preview type instead of reusing the generic image path.

Going Deeper

If the preview row props are not enough, the next customization steps are:

  1. Wrap the list with a custom layout using renderChannels.
  2. Replace pagination with a custom Paginator.
  3. Replace only the preview action surface through ChannelPreviewActionButtons in ComponentContext when the row UI itself can stay close to default.