Chart
Recharts wired to the okibi tokens — a responsive container with a HUD-skinned tooltip and legend, driven by a config-based color system.
"use client";import { TrendingUp } from "lucide-react";import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardIndex, CardTitle,} from "@/registry/okibi/ui/card";import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, type ChartConfig,} from "@/registry/okibi/ui/chart";const chartData = [ { cycle: "0x01", uplink: 186, downlink: 80 }, { cycle: "0x02", uplink: 305, downlink: 200 }, { cycle: "0x03", uplink: 237, downlink: 120 }, { cycle: "0x04", uplink: 173, downlink: 190 }, { cycle: "0x05", uplink: 209, downlink: 130 }, { cycle: "0x06", uplink: 264, downlink: 140 },];const chartConfig = { uplink: { label: "Uplink", color: "var(--chart-1)" }, downlink: { label: "Downlink", color: "var(--chart-2)" },} satisfies ChartConfig;export default function ChartDemo() { return ( <Card className="w-full max-w-xl"> <CardHeader> <CardIndex>AREA // STACKED</CardIndex> <CardTitle>Channel Throughput</CardTitle> <CardDescription>Uplink / downlink across six cycles</CardDescription> </CardHeader> <CardContent> <ChartContainer config={chartConfig}> <AreaChart accessibilityLayer={false} data={chartData} margin={{ left: 12, right: 12 }} > <defs> <linearGradient id="fill-uplink" x1="0" y1="0" x2="0" y2="1"> <stop offset="5%" stopColor="var(--color-uplink)" stopOpacity={0.7} /> <stop offset="95%" stopColor="var(--color-uplink)" stopOpacity={0.05} /> </linearGradient> <linearGradient id="fill-downlink" x1="0" y1="0" x2="0" y2="1"> <stop offset="5%" stopColor="var(--color-downlink)" stopOpacity={0.7} /> <stop offset="95%" stopColor="var(--color-downlink)" stopOpacity={0.05} /> </linearGradient> </defs> <CartesianGrid vertical={false} /> <XAxis dataKey="cycle" tickLine={false} axisLine={false} tickMargin={8} /> <ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} /> <Area dataKey="downlink" type="linear" fill="url(#fill-downlink)" stroke="var(--color-downlink)" strokeWidth={2} stackId="a" /> <Area dataKey="uplink" type="linear" fill="url(#fill-uplink)" stroke="var(--color-uplink)" strokeWidth={2} stackId="a" /> <ChartLegend content={<ChartLegendContent />} /> </AreaChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-start gap-1"> <div className="flex items-center gap-2 font-ui text-sm uppercase tracking-wide"> Up 12.4% this window <TrendingUp className="size-4 text-signal-spark" /> </div> <div className="font-mono text-xs text-muted-foreground"> CYCLE 0x01 — 0x06 // TELEMETRY NOMINAL </div> </CardFooter> </Card> );}npx shadcn@latest add https://okibi.cndr.dev/r/chart.jsonCharts are not a component library — they're a thin wrapper around
Recharts. You bring the chart; okibi gives it the HUD
skin (sharp corners, hard offset shadows, mono telemetry labels) and a
config-driven color system wired to the shared --chart-* tokens. Compose
ChartContainer, ChartTooltip and ChartLegend with Recharts primitives,
then copy the result into your app.
Installation
Install the theme first (it ships the --chart-1…5 palette), then add the
component:
npx shadcn@latest add https://okibi.cndr.dev/r/chart.jsonYour First Chart
Let's build a bar chart, then layer on a grid, an axis, a tooltip and a legend.
Add your data
Start with the data. It can be anything — an array of objects is the common shape. Here, packet counts per cycle:
const chartData = [
{ cycle: "0x01", packets: 186 },
{ cycle: "0x02", packets: 305 },
{ cycle: "0x03", packets: 237 },
{ cycle: "0x04", packets: 173 },
{ cycle: "0x05", packets: 209 },
{ cycle: "0x06", packets: 264 },
];Add a chart config
The config holds everything that isn't data — labels, colors and (optionally) icons. Keeping it separate means you can drive content and theming without touching your data source.
import { type ChartConfig } from "@/components/ui/chart";
const chartConfig = {
packets: {
label: "Packets",
color: "var(--chart-1)",
},
} satisfies ChartConfig;Build the chart
Wrap a Recharts chart in ChartContainer and pass the config. Each config key
is exposed to the chart as a --color-<key> CSS variable, so reference your
series color as var(--color-packets):
import { Bar, BarChart } from "recharts";
import { ChartContainer } from "@/components/ui/chart";
<ChartContainer config={chartConfig}>
<BarChart data={chartData}>
<Bar dataKey="packets" fill="var(--color-packets)" />
</BarChart>
</ChartContainer>Add a grid
import { CartesianGrid } from "recharts";
<CartesianGrid vertical={false} />Add an axis
import { XAxis } from "recharts";
<XAxis dataKey="cycle" tickLine={false} axisLine={false} tickMargin={8} />Add a tooltip
ChartTooltip is Recharts' tooltip; ChartTooltipContent is the HUD-skinned
body that reads labels and colors straight from your config:
import { ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />That's a complete chart. Here it is live — the Code tab is the full source:
"use client";import { TrendingUp } from "lucide-react";import { Bar, BarChart, CartesianGrid, XAxis } from "recharts";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardIndex, CardTitle,} from "@/registry/okibi/ui/card";import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig,} from "@/registry/okibi/ui/chart";const chartData = [ { cycle: "0x01", packets: 186 }, { cycle: "0x02", packets: 305 }, { cycle: "0x03", packets: 237 }, { cycle: "0x04", packets: 173 }, { cycle: "0x05", packets: 209 }, { cycle: "0x06", packets: 264 },];const chartConfig = { packets: { label: "Packets", color: "var(--chart-1)" },} satisfies ChartConfig;export default function BarVertical() { return ( <Card className="w-full"> <CardHeader> <CardIndex>BAR // VERTICAL</CardIndex> <CardTitle>Packet Volume</CardTitle> <CardDescription>Per-cycle packet counts</CardDescription> </CardHeader> <CardContent> <ChartContainer config={chartConfig}> <BarChart accessibilityLayer={false} data={chartData} margin={{ left: 12, right: 12 }} > <CartesianGrid vertical={false} /> <XAxis dataKey="cycle" tickLine={false} axisLine={false} tickMargin={8} /> <ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} /> <Bar dataKey="packets" fill="var(--color-packets)" radius={0} /> </BarChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-start gap-1"> <div className="flex items-center gap-2 font-ui text-sm uppercase tracking-wide"> Peak 305 at 0x02 <TrendingUp className="size-4 text-signal-spark" /> </div> <div className="font-mono text-xs text-muted-foreground"> CYCLE 0x01 — 0x06 // 1,374 TOTAL </div> </CardFooter> </Card> );}Add a legend
A legend earns its place once you have more than one series. Add ChartLegend
with ChartLegendContent and it maps each color back to its config label:
import { ChartLegend, ChartLegendContent } from "@/components/ui/chart";
<ChartLegend content={<ChartLegendContent />} />"use client";import { Radio } from "lucide-react";import { CartesianGrid, Line, LineChart, XAxis } from "recharts";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardIndex, CardTitle,} from "@/registry/okibi/ui/card";import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, type ChartConfig,} from "@/registry/okibi/ui/chart";const chartData = [ { cycle: "0x01", alpha: 186, beta: 80, gamma: 120 }, { cycle: "0x02", alpha: 305, beta: 200, gamma: 150 }, { cycle: "0x03", alpha: 237, beta: 120, gamma: 190 }, { cycle: "0x04", alpha: 173, beta: 190, gamma: 110 }, { cycle: "0x05", alpha: 209, beta: 130, gamma: 175 }, { cycle: "0x06", alpha: 264, beta: 140, gamma: 205 },];const chartConfig = { alpha: { label: "Alpha", color: "var(--chart-1)" }, beta: { label: "Beta", color: "var(--chart-3)" }, gamma: { label: "Gamma", color: "var(--chart-5)" },} satisfies ChartConfig;export default function LineMultiple() { return ( <Card className="w-full"> <CardHeader> <CardIndex>LINE // MULTIPLE</CardIndex> <CardTitle>Multi-Channel Trace</CardTitle> <CardDescription>Three channels overlaid</CardDescription> </CardHeader> <CardContent> <ChartContainer config={chartConfig}> <LineChart accessibilityLayer={false} data={chartData} margin={{ left: 12, right: 12 }} > <CartesianGrid vertical={false} /> <XAxis dataKey="cycle" tickLine={false} axisLine={false} tickMargin={8} /> <ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} /> <Line dataKey="alpha" type="linear" stroke="var(--color-alpha)" strokeWidth={2} dot={false} /> <Line dataKey="beta" type="linear" stroke="var(--color-beta)" strokeWidth={2} dot={false} /> <Line dataKey="gamma" type="linear" stroke="var(--color-gamma)" strokeWidth={2} dot={false} /> <ChartLegend content={<ChartLegendContent />} /> </LineChart> </ChartContainer> </CardContent> <CardFooter className="flex-col items-start gap-1"> <div className="flex items-center gap-2 font-ui text-sm uppercase tracking-wide"> 3 channels nominal <Radio className="size-4 text-signal-spark" /> </div> <div className="font-mono text-xs text-muted-foreground"> CYCLE 0x01 — 0x06 // MUX TRACE </div> </CardFooter> </Card> );}Chart config
ChartConfig decouples appearance from data. Each key matches a dataKey (or a
nameKey) in your chart and can set a label, an icon, and either a single
color or per-theme colors:
import { Cpu, MemoryStick } from "lucide-react";
import { type ChartConfig } from "@/components/ui/chart";
const chartConfig = {
compute: {
label: "Compute",
icon: Cpu,
color: "var(--chart-1)",
},
memory: {
label: "Memory",
icon: MemoryStick,
// Per-theme colors instead of a single `color`:
theme: { light: "var(--chart-2)", dark: "var(--chart-2)" },
},
} satisfies ChartConfig;Define the config once — the chart, the tooltip and the legend all read from it.
Theming
Charts pull their colors from the shared tokens. okibi ships a five-step chart
palette — --chart-1 through --chart-5 (scarlet → warning → success → cyan →
special) — already defined for both light and dark.
CSS variables
Reference a token in your config and ChartContainer exposes it to the chart as
--color-<key>:
const chartConfig = {
uplink: { label: "Uplink", color: "var(--chart-1)" },
downlink: { label: "Downlink", color: "var(--chart-2)" },
} satisfies ChartConfig;Using colors
Once the config is set, reference the generated variable anywhere Recharts takes a color:
<Bar dataKey="uplink" fill="var(--color-uplink)" />
<Line dataKey="uplink" stroke="var(--color-uplink)" />The tooltip and legend read the same config, so they stay in sync
automatically. To recolor a chart, point the config at different --chart-*
steps (or any token) — nothing in the chart body changes. Because every value
flows from the shared tokens, the chart renders identically here and in your app
after install.
Tooltip
A tooltip has a label, a per-series indicator, a name and a value.
ChartTooltipContent accepts:
| Prop | Description |
|---|---|
indicator | Indicator style: "dot" (default), "line" or "dashed". |
hideLabel | Hide the top label row. |
hideIndicator | Hide the per-series color swatch. |
nameKey | Config key to use for each series' name. |
labelKey | Config key to use for the label. |
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} />Legend
ChartLegend is Recharts' legend; ChartLegendContent renders the HUD swatches
and labels. It accepts:
| Prop | Description |
|---|---|
nameKey | Config key to use for each item's label. |
verticalAlign | "bottom" (default) or "top". |
hideIcon | Hide the color swatch / config icon. |
<ChartLegend content={<ChartLegendContent />} />Accessibility
Recharts can add keyboard navigation and screen-reader support via the
accessibilityLayer prop on the chart:
<BarChart accessibilityLayer data={chartData}>The gallery demos opt out (accessibilityLayer={false}) because they're
display-only HUD readouts that reveal nothing on interaction — that drops the
stray focus ring and tab stop. Turn it on for charts users actually explore.
More charts
This page teaches the API. For the full catalog — every family rendered in the okibi skin, each with copy-paste source — see the Charts gallery: