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 (default 640px) → 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}
/>;

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();
  // ...
};
ValueDescriptionType
layoutThe current layout."tabs" | "inline"
historyThe current navigation stack (array of { id } routes).SectionNavigatorRoute[]
historyPushNavigate to a route. In tabs layout this replaces history; in inline it pushes onto the stack.(route: { id: string }) => void
historyPopGo back one entry (no-op at the root).() => void
isNavigationOpenWhether the drawer overlay is open (inline layout only).boolean
openNavigationOpen the drawer overlay (inline layout).() => void
closeNavigationClose 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

NameDescriptionTypeDefault
sectionsThe sections to render. Required.SectionNavigatorSection[]-
defaultLayoutLayout used before the container is measured."tabs" | "inline""tabs"
layoutControlled layout. When set, the responsive observer is disabled."tabs" | "inline"-
tabsLayoutMinWidthMinimum container width (px) for the tabs layout.number640
onLayoutChangeCalled whenever the resolved layout changes (and once on mount).(layout) => void-
initialHistoryInitial navigation stack. Defaults to the first section.SectionNavigatorRoute[]-
createLayoutObserverFactory that wires up layout detection and returns a cleanup function.SectionNavigatorLayoutObserverFactorywidth observer
classNameAdditional class name applied to the root element. Other div attributes are forwarded.string-

SectionNavigatorSection

FieldDescriptionType
idStable unique identifier for the section.string
NavButtonNavigation entry. Receives sectionId, selected, and select.ComponentType<SectionNavigatorNavButtonProps>
SectionContentPanel rendered when the section is active. Receives layout.ComponentType<SectionNavigatorSectionContentProps>

SectionNavigatorNavButtonProps extends the native button props with sectionId: string, selected: boolean, and select: () => void.