Data display / Charts

Charts

<ChartContainer />Stable

Token-driven recharts primitives plus the dashboard blocks built on them: MetricCard (label · info tooltip · tabular value · delta · sparkline) and ChartTabs (metric tabs over a swappable line/bar/area panel). Data colors come from a dedicated categorical palette (--chart-1 … --chart-12) plus sequential and diverging scales — never the semantic status colors. Tooltips, legends, axes, and grids are pre-styled to match the system and adapt to dark mode automatically.

import { ChartContainer, ChartSparkline, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, MetricCard, ChartTabs } from "@/components/upnext";

Data color palette

The three data scales. Categorical for unordered series, sequential for ordered intensity, diverging for signed values around a midpoint.

Categorical · 12 series

Unordered, equal-weight series. Pull colors in order — var(--chart-1) … var(--chart-12). Oklch-tuned so adjacent series stay distinct. Never use the semantic status colors for data.

cobalt
orange
teal
magenta
lime
violet
crimson
sky
cocoa
pink
emerald
mustard

Sequential · single-hue ramp

Ordered / continuous data (intensity, density, heatmaps). var(--chart-sequential-1…6), low → high. Any tone ramp can serve as a sequential scale.

Diverging · around a midpoint

Signed data that diverges from a neutral zero (gains vs losses, above/below target). var(--chart-diverging-1…7) — crimson ← neutral → cobalt.

Variants

Each type composes recharts directly (shadcn's model) inside ChartContainer. Series colors are assigned via the config's color field and referenced as var(--color-{key}); per-datum charts (donut, scatter, sankey) use seriesColor(i) from @/lib/chart.

const config = {
commits: { label: "Commits", color: "var(--chart-1)" },
offers: { label: "Offers", color: "var(--chart-2)" },
visits: { label: "Visits", color: "var(--chart-3)" },
} satisfies ChartConfig
<ChartContainer config={config} className="h-[260px] w-[440px]">
<BarChart accessibilityLayer data={data}>
<CartesianGrid vertical={false} />
<XAxis dataKey="week" tickLine={false} axisLine={false} />
<YAxis tickFormatter={formatCompact} />
<ChartTooltip content={<ChartTooltipContent />} />
<ChartLegend content={<ChartLegendContent />} />
<Bar dataKey="commits" fill="var(--color-commits)" radius={4} />
<Bar dataKey="offers" fill="var(--color-offers)" radius={4} />
<Bar dataKey="visits" fill="var(--color-visits)" radius={4} />
</BarChart>
</ChartContainer>

Composition

The dashboard blocks: MetricCard (every state — info tooltip, direction-derived delta tones with overrides, metadata, sparkline slot), ChartTabs (metric tabs over swappable area/bar panels, full keyboard + aria via Radix Tabs, selected = tint + accent at regular weight), and charts inside plain Card surfaces.

Transfers
1,118+1215%
vs last season
Withdrawals
198-7%
vs last season
Avg. days in portal
41-12%
down is good — faster placements
Open evaluations
270%
unchanged this week

The Transfers card is the interactive variant — hover it, or click through (info stays independently clickable).

{/* interactive variant (opt-in): href or onClick makes the whole card
the target — hover = soft accent fill only, press scale, focus ring;
the info tooltip stays independently clickable above the overlay */}
<MetricCard
label="Transfers"
info="Athletes who entered the portal and moved programs this season."
value="1,118"
delta={{ value: "+1215%", direction: "up" }}
metadata="vs last season"
trailing={<ChartSparkline data={trend} color="var(--chart-1)" className="w-20" />}
href="/reports/transfers"
/>
{/* down can be good — override the direction-derived tone */}
<MetricCard label="Avg. days in portal" value="41"
delta={{ value: "-12%", direction: "down", tone: "success" }}
metadata="down is good — faster placements" />

Source

The wrapper is a thin re-export; the actual styling lives in the shadcn primitive.