M
MetricUI

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.

Total Revenue
$0
Active Users
0
Churn Rate
0%
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

MRR
$0
ARR
$0

Charts

Chart content placeholder.

Table

Table content placeholder.
<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.

Filters
Total Revenue
$0
<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" },
  ]}
/>

Keyboard Navigation

DashboardNav implements full ARIA tablist keyboard navigation:

  • Arrow Right / Arrow Down — move to the next tab
  • Arrow Left / Arrow Up — move to the previous tab
  • Home — jump to the first tab
  • End — jump to the last tab

Focus moves automatically and the corresponding onChange fires, keeping keyboard and pointer interactions in parity.

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

PropTypeDefaultDescription
tabsDashboardNavTab[](required)Array of tab definitions. Each tab has value, label, and optional icon, badge, and badgeFormat.
valuestringControlled active tab value.
defaultValuestringFirst tabDefault active tab for uncontrolled usage.
onChange(value: string) => voidCallback 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.
syncUrlstringURL search param name. Persists the active tab in the URL for deep-linking.
stickybooleanfalseStick to the viewport top with frosted-glass backdrop blur.
size"sm" | "md" | "lg""md"Size variant controlling text, padding, and icon sizing.
densebooleanfalseCompact layout. Falls back to MetricProvider config.
variantCardVariantVisual variant. Falls back to MetricProvider config.
classNamestringAdditional CSS classes on the root element.
idstringHTML id attribute.
data-testidstringTest id for testing frameworks.

DashboardNavTab

PropTypeDefaultDescription
valuestring(required)Unique identifier for the tab.
labelstring(required)Display text.
iconReactNodeIcon rendered before the label.
badgenumber | stringBadge value. Numbers are formatted; strings render as-is.
badgeFormatFormatOptionFormat 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.