Layout
DashboardNav
Tabbed navigation for switching dashboard views or smooth-scrolling to page sections. Supports controlled and uncontrolled modes, URL sync, live badges, keyboard navigation, and nests cleanly inside FilterBar via the FilterBar.Nav slot.
Overview
DashboardNav renders a horizontal tab strip with a sliding underline indicator. In tabs mode (default), use value / onChange to control which content panel is visible. In scroll mode, clicking a tab smooth-scrolls to the matching section ID and an IntersectionObserver keeps the active tab in sync as the user scrolls.
<DashboardNav
tabs={[
{ value: "overview", label: "Overview" },
{ value: "revenue", label: "Revenue" },
{ value: "customers", label: "Customers" },
{ value: "settings", label: "Settings" },
]}
value={activeTab}
onChange={setActiveTab}
/>Tab Mode
The default mode. Use controlled value / onChange to conditionally render content below the nav. Click the tabs to swap the content panel.
const [activeTab, setActiveTab] = useState("overview");
<Dashboard>
<DashboardNav
tabs={tabs}
value={activeTab}
onChange={setActiveTab}
/>
{activeTab === "overview" && (
<MetricGrid columns={3}>
<KpiCard title="Total Revenue" value={128400} format={{ style: "currency" }} />
<KpiCard title="Active Users" value={3842} format="compact" />
<KpiCard title="Churn Rate" value={0.024} format={{ style: "percent" }} />
</MetricGrid>
)}
{activeTab === "revenue" && <KpiCard title="Monthly Revenue" value={42800} />}
</Dashboard>Scroll Mode
Set mode="scroll" and give each page section an id that matches the tab value. Clicking a tab smooth-scrolls to the section, and the active tab updates automatically via IntersectionObserver as the user scrolls. Pair with sticky for a fixed nav that stays in view.
Metrics
Charts
Table
<DashboardNav
tabs={[
{ value: "section-metrics", label: "Metrics" },
{ value: "section-charts", label: "Charts" },
{ value: "section-table", label: "Table" },
]}
mode="scroll"
sticky
/>
<div id="section-metrics">...</div>
<div id="section-charts">...</div>
<div id="section-table">...</div>Inside FilterBar
Nest DashboardNav inside the FilterBar.Nav slot to render it as the top row of the filter bar. Filters sit below in FilterBar.Primary. This keeps navigation and filtering in a single, cohesive bar.
<FilterBar>
<FilterBar.Nav>
<DashboardNav
tabs={tabs}
value={activeTab}
onChange={setActiveTab}
/>
</FilterBar.Nav>
<FilterBar.Primary>
<PeriodSelector presets={["7d", "30d", "90d"]} />
<DropdownFilter label="Region" dimension="region" options={regions} />
</FilterBar.Primary>
</FilterBar>Badges
Each tab can display a live badge. Numeric badges are formatted through the format engine (e.g., 1489 becomes "1.5K" with badgeFormat: "compact"). String badges render as-is.
<DashboardNav
tabs={[
{ value: "alerts", label: "Alerts", badge: 12 },
{ value: "users", label: "Users", badge: 1489, badgeFormat: "compact" },
{ value: "tasks", label: "Tasks", badge: "NEW" },
]}
/>URL Sync
Pass a syncUrl param name to persist the active tab in the URL search params. The component reads the initial value from the URL on mount, and updates it via history.replaceState on each tab change, making dashboards deep-linkable and shareable.
// URL will update to ?view=revenue when the tab is clicked
<DashboardNav
tabs={tabs}
syncUrl="view"
value={activeTab}
onChange={setActiveTab}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
tabs | DashboardNavTab[] | (required) | Array of tab definitions. Each tab has value, label, and optional icon, badge, and badgeFormat. |
value | string | — | Controlled active tab value. |
defaultValue | string | First tab | Default active tab for uncontrolled usage. |
onChange | (value: string) => void | — | Callback fired when the active tab changes. |
mode | "tabs" | "scroll" | "tabs" | In "tabs" mode, use value/onChange to swap content. In "scroll" mode, clicking scrolls to the matching section ID. |
syncUrl | string | — | URL search param name. Persists the active tab in the URL for deep-linking. |
sticky | boolean | false | Stick to the viewport top with frosted-glass backdrop blur. |
size | "sm" | "md" | "lg" | "md" | Size variant controlling text, padding, and icon sizing. |
dense | boolean | false | Compact layout. Falls back to MetricProvider config. |
variant | CardVariant | — | Visual variant. Falls back to MetricProvider config. |
className | string | — | Additional CSS classes on the root element. |
id | string | — | HTML id attribute. |
data-testid | string | — | Test id for testing frameworks. |
DashboardNavTab
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | (required) | Unique identifier for the tab. |
label | string | (required) | Display text. |
icon | ReactNode | — | Icon rendered before the label. |
badge | number | string | — | Badge value. Numbers are formatted; strings render as-is. |
badgeFormat | FormatOption | — | Format option for numeric badges (e.g., "compact", { style: "percent" }). |
Notes
- DashboardNav uses forwardRef and passes through id, data-testid, and className.
- In scroll mode, an IntersectionObserver highlights the section currently in view. A 1-second lock prevents the observer from overriding the active tab immediately after a click-to-scroll.
- The sliding underline indicator animates with a 200ms cubic-bezier transition.
- Sticky mode applies frosted-glass styling (backdrop-blur-xl, 80% card-bg opacity) and sticks to the viewport top with z-index 31.
- When dense is true and size is "md", the component automatically downsizes to "sm" for compact layouts.
- Badge formatting uses the same format engine as KpiCard. Pass badgeFormat: "compact" for abbreviated numbers.
- Full ARIA tablist semantics: role="tablist" on the container, role="tab" and aria-selected on each button.
- Works both standalone and inside FilterBar.Nav. When inside FilterBar, omit the sticky prop — FilterBar handles sticking.