MetricUIMetricUI
UI

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 inside FilterBar via FilterBar.Nav.

import { DashboardNav } from "metricui";

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

PropTypeDescription
tabs*
DashboardNavTab[]

Array of tab definitions. Each: { value, label, icon?, badge?, badgeFormat? }.

value
string

Controlled active tab value.

defaultValue
string

Default active tab for uncontrolled usage.

onChange
(value: string) => void

Callback fired when the active tab changes.

mode
"tabs" | "scroll"

Navigation mode. "tabs" swaps content via value/onChange. "scroll" smooth-scrolls to section IDs matching tab values.

syncUrl
string

URL search param name. Persists the active tab in the URL for deep-linking.

sticky
boolean

Stick to viewport top with frosted-glass backdrop blur.

size
"sm" | "md" | "lg"

Size variant controlling text, padding, and icon sizing.

dense
boolean

Compact layout. Falls back to MetricProvider config.

variant
CardVariant

Visual variant. Falls back to MetricProvider config.

className
string

Additional CSS classes.

id
string

HTML id attribute.

data-testid
string

Test id.

Data Shape

interface DashboardNavTab {
  value: string;
  label: string;
  icon?: ReactNode;
  badge?: number | string;
  badgeFormat?: FormatOption;
}

Notes

  • Uses forwardRef. 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'.
  • Badge formatting uses the same format engine as KpiCard.
  • 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.
  • The aiContext prop (inherited from BaseComponentProps) adds business context for AI Insights analysis. See the AI Insights guide for details.