import { ChannelList } from "stream-chat-react";
<ChannelList Preview={CustomChannelPreview} />;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 forrenderChannels. - Preserve the row click behavior so the active channel stays in sync with
Channel. - Prefer
displayTitle,displayImage, andlatestMessagePreviewfrom preview props over recomputing everything manually. - If you build preview text from
useLatestMessagePreview(), handle nativegiphymessages separately from ordinary images. - Use
aria-pressedfor 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:
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:
- Wrap the list with a custom layout using
renderChannels. - Replace pagination with a custom
Paginator. - Replace only the preview action surface through
ChannelPreviewActionButtonsinComponentContextwhen the row UI itself can stay close to default.