Skip to content

Components

Tabs

Sticky sub-navigation, solved once: a tab row that docks below the Header through the --header-height variable the Header publishes, with real tablist semantics and arrow-key navigation.

Docked below the header

Because the Header keeps --header-height up to date via a ResizeObserver, the row stays glued below it whatever rows the header shows — no hand-tuned offsets. Selection is controlled (value/onValueChange) or uncontrolled (defaultValue, falling back to the first tab); you render the matching panel from the current value.

Open fullscreen
9:41
profile.tsx
import { AppShell, Header, Content, Tabs, Tab } from "appshell-react";
import { useState } from "react";

export default function Profile() {
  const [tab, setTab] = useState("posts");

  return (
    <AppShell safeArea>
      <Header behavior="fixed" title="Nadia Reyes" subtitle="@nadia" />

      {/* Docks itself below the fixed header — no manual top offset */}
      <Tabs value={tab} onValueChange={setTab} aria-label="Profile sections">
        <Tab value="posts" label="Posts" />
        <Tab value="replies" label="Replies" badge={<Badge>3</Badge>} />
        <Tab value="media" label="Media" />
      </Tabs>

      <Content>
        {tab === "posts" && <Posts />}
        {tab === "replies" && <Replies />}
        {tab === "media" && <MediaGrid />}
      </Content>
    </AppShell>
  );
}

Accessibility

The row is a real role="tablist": tabs carry aria-selected, only the active one is in the Tab order, and ArrowLeft/ArrowRight move and select with wrap-around at the ends.

Pass sticky={false} for an inline, non-docking row — segmented filters inside content, for example.

API

PropTypeDefaultDescription
valuestring—Controlled selected value.
defaultValuestring—Uncontrolled initial value. Defaults to the first Tab.
onValueChange(value: string) => void—Selection handler.
stickybooleantrueDock below the Header via --header-height. false renders an inline row.
classNamestring—Extra classes for the row.
aria-labelstring"Tabs"Accessible tablist name.
children*ReactNode—Tab elements.
PropTypeDefaultDescription
value*string—Identifies this tab in value/onValueChange.
label*string—Tab text.
badgeReactNode—Trailing count or dot.
classNamestring—Extra classes.