Section Navigator
SectionNavigator is the generic, channel-agnostic navigation primitive that powers ChannelDetail. It renders a set of sections — each pairing a navigation button with a content panel — and switches between two layouts based on its container width:
tabs— a docked navigation rail rendered next to the active section's content.inline— a single-column view where navigation lives behind a hamburger button that opens a drawer overlay.
SectionNavigator knows nothing about channels; you can reuse it for any multi-section surface (settings panels, profile views, app menus). ChannelDetail is essentially SectionNavigator preconfigured with channel sections and a channel provider.
Installation
import {
SectionNavigator,
SectionNavigatorHeader,
useSectionNavigatorContext,
} from "stream-chat-react/channel-detail";
import "stream-chat-react/dist/css/channel-detail.css";Defining sections
A section is a { id, NavButton, SectionContent } descriptor. NavButton renders the entry in the navigation rail/drawer; SectionContent renders the panel.
import { SectionNavigator } from "stream-chat-react/channel-detail";
const sections = [
{
// Give each section a stable, unique `id` — the navigator tracks the active
// route and history by `id`, so it must stay constant across renders.
id: "general",
NavButton: ({ select, selected }) => (
<button onClick={select} aria-pressed={selected}>
General
</button>
),
SectionContent: () => <GeneralSettings />,
},
{
id: "advanced",
NavButton: ({ select, selected }) => (
<button onClick={select} aria-pressed={selected}>
Advanced
</button>
),
SectionContent: () => <AdvancedSettings />,
},
];
const Settings = () => (
// Render inside a sized container — the default layout observer measures this
// parent to choose the layout (wide → `tabs`, narrow → `inline`).
<div style={{ width: "100%", height: "100%" }}>
<SectionNavigator sections={sections} />
</div>
);The first section is selected by default. Pass initialHistory to start on a different section.
Layout
The layout is resolved automatically by a ResizeObserver on the navigator's parent element:
- Container width
< tabsLayoutMinWidth(default640px) →inline. - Otherwise →
tabs.
Set defaultLayout for the value used before the first measurement, and onLayoutChange to react to layout changes (it fires once on mount and on every change).
<SectionNavigator
sections={sections}
defaultLayout="inline"
tabsLayoutMinWidth={720}
onLayoutChange={(layout) => console.log("layout:", layout)}
/>Controlled layout
Drive tabs/inline from width by default; pass layout only when you need to override the responsive behavior, which forces a layout and disables the observer:
// Controlled mode — opt out of width-based switching and pin the layout.
<SectionNavigator sections={sections} layout="tabs" />Custom layout observer
Override how the layout is computed with createLayoutObserver. The factory receives the root element, a setLayout callback, and tabsLayoutMinWidth, and returns a cleanup function:
const createLayoutObserver = ({ element, setLayout, tabsLayoutMinWidth }) => {
const onResize = () =>
setLayout(element.clientWidth < tabsLayoutMinWidth ? "inline" : "tabs");
window.addEventListener("resize", onResize);
onResize();
return () => window.removeEventListener("resize", onResize);
};
<SectionNavigator
sections={sections}
createLayoutObserver={createLayoutObserver}
/>;Navigation state with useSectionNavigatorContext
Section content (and nav buttons) can read and drive navigation through useSectionNavigatorContext:
import { useSectionNavigatorContext } from "stream-chat-react/channel-detail";
const NestedDetail = ({ goBack }) => {
// Read navigation state from inside section content rather than threading it
// down through props.
const { layout, historyPush, historyPop, isNavigationOpen, openNavigation } =
useSectionNavigatorContext();
// ...
};| Value | Description | Type |
|---|---|---|
layout |
The current layout. | "tabs" | "inline" |
history |
The current navigation stack (array of { id } routes). |
SectionNavigatorRoute[] |
historyPush |
Navigate to a route. In tabs layout this replaces history; in inline it pushes onto the stack. |
(route: { id: string }) => void |
historyPop |
Go back one entry (no-op at the root). | () => void |
isNavigationOpen |
Whether the drawer overlay is open (inline layout only). |
boolean |
openNavigation |
Open the drawer overlay (inline layout). |
() => void |
closeNavigation |
Close the drawer overlay. | () => void |
SectionNavigatorHeader
Use SectionNavigatorHeader as the header for each section's content. It renders a Prompt.Header and, in inline layout where the navigation rail is hidden, prepends a hamburger button that opens the drawer. The hamburger is omitted when a goBack handler is set (nested views show a back button instead, so the two affordances don't compete).
import { SectionNavigatorHeader } from "stream-chat-react/channel-detail";
const GeneralSettings = () => (
<>
{/* Use SectionNavigatorHeader for each section's header: it shows the
hamburger in `inline` layout and a back button on nested views. */}
<SectionNavigatorHeader>General</SectionNavigatorHeader>
{/* body */}
</>
);SectionNavigatorHeader accepts all Prompt.Header props except LeadingContent, which it manages to render the hamburger.
Props
| Name | Description | Type | Default |
|---|---|---|---|
sections |
The sections to render. Required. | SectionNavigatorSection[] |
- |
defaultLayout |
Layout used before the container is measured. | "tabs" | "inline" |
"tabs" |
layout |
Controlled layout. When set, the responsive observer is disabled. | "tabs" | "inline" |
- |
tabsLayoutMinWidth |
Minimum container width (px) for the tabs layout. |
number |
640 |
onLayoutChange |
Called whenever the resolved layout changes (and once on mount). | (layout) => void |
- |
initialHistory |
Initial navigation stack. Defaults to the first section. | SectionNavigatorRoute[] |
- |
createLayoutObserver |
Factory that wires up layout detection and returns a cleanup function. | SectionNavigatorLayoutObserverFactory |
width observer |
className |
Additional class name applied to the root element. Other div attributes are forwarded. |
string |
- |
SectionNavigatorSection
| Field | Description | Type |
|---|---|---|
id |
Stable unique identifier for the section. | string |
NavButton |
Navigation entry. Receives sectionId, selected, and select. |
ComponentType<SectionNavigatorNavButtonProps> |
SectionContent |
Panel rendered when the section is active. Receives layout. |
ComponentType<SectionNavigatorSectionContentProps> |
SectionNavigatorNavButtonProps extends the native button props with sectionId: string, selected: boolean, and select: () => void.