Welcome to Oshon · v1.0  ·  Now in public beta for enterprise teams Read the launch notes
Data VisualizationUpdatedPro· Data Visualization packWCAG 2.2 AA

Widget · Tracking Item

276×288 KPI tracker — 2×2 metric grid · header tooltips · per-cell drill-down · up/down/neutral trend tones retinted by applyTheme().

FigmaStorybookSource · PronpmPro

Unlock Widget · Tracking Item

2×2 metric tracker with per-cell drill-down + semantic trend tones. Part of the Data Visualization pack.

Data Visualization pack starts at $249/yr Y1 (renews at $174/yr). Or get every Pro pack with Solo for $999/yr.

Preview

Live preview
@oshon-ai/components
Figma-parity authoring sample
Beef Price TrackerLast 12 months
TRACKING ITEM
Avg. Price(per lb)
JUL '21/'20
$2.48/$2.55
% Change
JUL '21/'20
-3.0%/-$0.07
Comparison
JUL '21/'20
+5.0%
Avg. Price vs PPI
% Change
-8.0%
Drill-down — clickable title + clickable cells
Q3 snapshot
HEALTH SUMMARY
Drill-down trace
Click the title or any of the four cells.
Trend tones across three trackers
Revenue
MONEY
MRR
this month
$680K
ARR
annualised
$8.2M
Growth
MoM
+12%
Target gap
to plan
-$60K
Retention
CUSTOMERS
Churn
30d
1.9%
NPS
last Q
62
Saves
this Q
+42
Refunds
rate
0.8%
Operations
OPS
P99 latency
edge
142ms
Errors
24h
0.04%
Uptime
30d
99.98%
Backlog
on-call
7 alerts
Kebab menu — popover-anchored, three actions
Pipeline
SALES
Open deals
this Q
42
Avg cycle
days
38
Win rate
QoQ
+4.0pp
Slipped
last week
3
Menu trace
Click the ⋮ kebab to open the popover-anchored menu.

Installation

Install the runtime packages:

pnpm
pnpm add @oshon-ai/components @oshon-ai/tokens @oshon-ai/primitives

Or scaffold the component source directly into your codebase (shadcn-style):

pnpm
pnpm dlx @oshon-ai/cli add widgettrackingitem

Wire the tokens into your Tailwind v4 stylesheet:

css
/* app/globals.css */
@import 'tailwindcss';
@import '@oshon-ai/tokens/css';
@import '@oshon-ai/tokens/tailwind';

New here? Walk through the full setup — prereqs, theming, your first render.

Usage

Import the component and render it. Every component supports the standard tier, size, and disabled props where applicable.

tsx
'use client';
import { Widget·TrackingItem } from '@oshon-ai/components';

export default function Example() {
  return <Widget·TrackingItem />;
}
Pro · Data Visualization

2×2 metric tracker with per-cell drill-down + semantic trend tones. Part of the Data Visualization pack.

Unlock Widget · Tracking Item

2×2 metric tracker with per-cell drill-down + semantic trend tones. Part of the Data Visualization pack.

Data Visualization pack starts at $249/yr Y1 (renews at $174/yr). Or get every Pro pack with Solo for $999/yr.

Styling

Three layers of customization, in order of escape-hatch strength: className overrides → data-attribute targeting → CSS custom properties.

Passing Tailwind classes

Every Oshon component accepts a className prop merged AFTER the component's default classes. Use it to override spacing, color, or size without forking the component.

tsx
<Widget·TrackingItem
  className="ring-2 ring-offset-2 ring-blue-500"
/>

Data attributes

Oshon components expose their internal state as data-oshon-* attributes so you can target them from CSS without coupling to internal class names. The most common attributes are listed below — see the component's source for the full set.

AttributeValuesDescription
data-oshon-sizexs · s · m · l · mobileVisual size axis. Mirrors the `size` prop.
data-oshon-tierprimary · secondary · tertiaryVisual emphasis tier (Button family). Mirrors the `tier` prop.
data-oshon-stateenabled · active · error · disabledComponent surface state. Set automatically based on props.
data-disabledtrue · (omitted)Set when `disabled` is true. Pair with `:disabled` CSS for native input components.
data-stateopen · closed · checked · unchecked · …Radix-derived state for overlay components (Dialog, Tabs, Toggle, etc.).
css
/* Target the secondary tier specifically */
[data-oshon-tier="secondary"] {
  --oshon-color-primary-700: var(--my-brand-color);
}

Interactive states

Every interactive component supports the standard CSS pseudo- classes plus Tailwind's state variants. Focus rings always use :focus-visible so keyboard users see them but mouse users don't.

  • :hover / hover:* — pointer hover
  • :focus-visible / focus-visible:* — keyboard focus
  • :active / active:* — pressed
  • :disabled / disabled:* — set via the disabled prop

Anatomy

The named regions a consumer composes when rendering this component. Each is documented separately so you can target keyboard nav, ARIA labels, and slot props with precision.

header

Top 48 px strip — title (Lato Bold 14, black, -0.28px tracking), subtitle (Lato Regular 12, neutral-500), trailing 24×24 kebab. Top corners radius 8 px. Title becomes a `<button>` with link styling when `onTitleClick` is supplied.

section-label

Lato Bold 14 uppercase string at left:20, top:60 — e.g. "TRACKING ITEM". +0.56px tracking, gray-800. Identifies the widget category at a glance and reads as a visual anchor between the header and the metric grid.

cell

One of four metric cells in the 2×2 grid. Layout: header row at top:12 (label + optional `(suffix)` + optional info-icon Tooltip trigger), caption row at top:36 (Lato Regular 11, neutral-500), value row near bottom (Lato Bold 16/20 in the trend tone — neutral / `--oshon-color-success-700` / `--oshon-color-error-700` — followed by optional secondary value in Lato Regular 14/20 gray-600 and optional 14×14 trend chevron). `applyTheme()` retints the trend tones via the semantic palette. Cell becomes a `<button>` with hover background tint + focus ring when `onClick` is supplied.

kebab

Three-dot vertical menu button at right:12 in the header. Three modes: (1) `moreItems` supplied → renders as a Popover.Trigger and the component owns the popover-anchored `Menu` open state (picks fire `onMoreAction(itemId)` and dismiss). (2) `onMore` supplied → plain `<button>` that fires the callback (consumer wires their own menu). (3) Neither → presentational `<span>` so the surface stays RSC-safe along the read-only path.

info-icon

Per-cell 14×14 info-circle that opens a Tooltip on hover / focus. Rendered only when the metric carries a `tooltip` string. The icon is a `<button>` so the tooltip is reachable via keyboard focus, in line with the WAI-ARIA tooltip pattern.

Keyboard

Header kebab is a `<button>` when `onMore` or `moreItems` is supplied (Tab to focus, Enter / Space to activate, Oshon focus ring); otherwise it renders as a presentational `<span>` with `aria-hidden="true"`. Title becomes a `<button>` with link styling when `onTitleClick` is supplied. Each metric cell becomes a full-cell `<button>` when its `onClick` is supplied — keyboard-activatable with the same focus + hover treatment as the title. Header info-icons are themselves `<button>`s that wrap a `Tooltip` so the tooltip is reachable via keyboard focus.

Accessibility

Every Oshon component ships axe-clean. We test in CI on every PR and publish the audit log per component.

WCAG level
2.2 AA
Screen readers tested
VoiceOver (macOS), NVDA (Windows)
Last axe audit
2026-04-29

Do / Don't

✓ Do

Default — beef-price tracker (Figma authoring sample)
<WidgetTrackingItem
  title="Beef Price Tracker"
  subtitle="Last 12 months"
  sectionLabel="TRACKING ITEM"
  primary={[
    { label: 'Avg. Price', labelSuffix: '(per lb)', caption: "JUL '21/'20",
      value: '$2.48', secondaryValue: '/$2.55' },
    { label: '% Change', caption: "JUL '21/'20",
      value: '-3.0%', secondaryValue: '/-$0.07' },
  ]}
  comparison={[
    { label: 'Comparison', tooltip: 'Year-over-year delta',
      caption: "JUL '21/'20", value: '+5.0%', trend: 'up' },
    { label: 'Avg. Price vs PPI', tooltip: 'Producer Price Index',
      caption: '% Change', value: '-8.0%', trend: 'down' },
  ]}
  onMore={() => openMenu()}
/>
Drill-down — clickable title + clickable cells
<WidgetTrackingItem
  title="Tracker"
  sectionLabel="TRACKING ITEM"
  primary={[
    { ...m1, onClick: () => router.push('/tracker/avg-price') },
    { ...m2, onClick: () => router.push('/tracker/change') },
  ]}
  comparison={[m3, m4]}
  onTitleClick={() => router.push('/dashboards/tracking')}
/>
Popover-anchored menu via `moreItems`
<WidgetTrackingItem
  title="Tracker"
  sectionLabel="TRACKING ITEM"
  primary={[m1, m2]}
  comparison={[m3, m4]}
  moreItems={[
    { id: 'view', label: 'View details' },
    { id: 'export', label: 'Export as CSV' },
  ]}
  onMoreAction={(id) => handle(id)}
/>
Composing a dashboard row with sibling widgets
<div style={{ display: "flex", gap: 8, alignItems: "flex-start" }}>
  <WidgetLeaderboard title="Top Vendors" items={items} />
  <WidgetTrackingItem title="Tracker" sectionLabel="TRACKING ITEM"
    primary={[m1, m2]} comparison={[m3, m4]} />
</div>

✗ Don't

Hardcoded dimension override
<WidgetTrackingItem title="x" sectionLabel="X" primary={[m1, m2]} comparison={[m3, m4]} style={{ width: 400 }} />

WidgetTrackingItem is authored for the 4×4 quarter slot — 276 × 288 px. Other slot sizes are served by sibling Widget* components. Forcing a different width breaks the dashboard grid math and ships a visual misalignment.

More than four metrics
<WidgetTrackingItem primary={[m1, m2] as any} comparison={[m3, m4, m5] as any} title="x" sectionLabel="X" />

The grid is fixed at exactly 2×2 — four metric cells. The `primary` and `comparison` props are typed as `[T, T]` tuples to make this a compile-time error. If you need an N-cell metric grid, reach for a sibling tracker widget or compose multiple WidgetTrackingItem instances side-by-side.

Trend tone disagreeing with the value sign
<WidgetTrackingItem comparison={[{ label: 'Δ', caption: 'YoY', value: '-15%', trend: 'up' }, m4] as any} title="x" sectionLabel="X" primary={[m1, m2]} />

`trend` is the visual signal of business-significance, not the sign of the number. Painting `-15%` as green up-trend is technically supported (e.g. cost reduction is "up" in a savings dashboard), but the consumer is responsible for keeping the tone aligned with the dashboard's domain semantics. Disagreement between sign and tone produces a visually confusing surface.

Design rationale

WidgetTrackingItem is the third member of the dashboard-widget family — a fixed-dim 276×288 surface authored for the same 4×4 quarter slot as WidgetLeaderboard / WidgetDonutChart, but tuned for at-a-glance "track this number" metric grids (avg price + delta + comparison + vs-PPI is the canonical Figma authoring). The 2×2 layout is locked at the type level via tuple props (`[T, T]`) so a misuse (3+ cells in a row) is a compile-time error. Outer dims do NOT scale with `size`; only typography density does. Trend tone is consumer-driven (`up` / `down` / `neutral`) — the widget paints the value + chevron in the matching semantic color (`--oshon-color-success-700` for up, `--oshon-color-error-700` for down) but does NOT infer trend from the value sign, because business-significance is a domain decision (cost reductions read as `up` in savings dashboards). Trend tones flow through `applyTheme()` so the deltas retint alongside the rest of the brand. Per-cell `onClick`, header `onTitleClick`, and the popover-anchored kebab Menu mirror WidgetLeaderboard exactly so dashboard widgets feel like a coherent family. Figma file `MsyCsSxIRkgRjWd1bSJpq6` node `12002:5481`.