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 TO 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 />} />Here is the complete chart. The Code tab contains 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 TO 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 TO 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), with values 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. This removes 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, with every family rendered in the okibi skin and paired with copy-paste source, see the Charts gallery: