Inkbox

Component

Chart

Line, area, bar and pie charts in SVG with lilac rules, 12px muted labels and a white floating tooltip.

npx -y github:anwarlemu/inkbox-brand-cli#release add chart
Preview
Line
Area
Pie

Usage

Use to show volume over time (messages sent, calls, webhook deliveries) or a share (deliveries by status). Name and color each series in a ChartConfig, wrap the chart in ChartContainer, and add ChartTooltip and ChartLegend. Keep to five series; the colors follow the series order.

Avoid

  • Gradients other than the area fade
  • 3D or shadowed marks
  • More than five series
  • Colors outside the series tokens

Exports: ChartConfig, AreaChart, BarChart, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, LineChart, PieChart, useChart. This component is a client component.

Example

examples/chart.tsx
"use client";​import { AreaChart, BarChart, type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, LineChart, PieChart } from "@/components/ui/chart";​const MESSAGES = [  { month: "January", email: 186, imessage: 80 },  { month: "February", email: 305, imessage: 200 },  { month: "March", email: 237, imessage: 120 },  { month: "April", email: 173, imessage: 190 },  { month: "May", email: 209, imessage: 130 },  { month: "June", email: 264, imessage: 140 },];​const config = {  email: { label: "Email" },  imessage: { label: "iMessage" },} satisfies ChartConfig;​const short = (v: string) => v.slice(0, 3);​export function Preview() {  return (    <ChartContainer config={config} className="aspect-auto h-80">      <BarChart data={MESSAGES} index="month" tickFormatter={short} aria-label="Messages sent per month by channel" />      <ChartTooltip content={<ChartTooltipContent />} />      <ChartLegend content={<ChartLegendContent />} />    </ChartContainer>  );}​export function Line() {  return (    <ChartContainer config={config} className="aspect-auto h-80">      <LineChart data={MESSAGES} index="month" tickFormatter={short} showYAxis dots aria-label="Messages sent per month" />      <ChartTooltip content={<ChartTooltipContent indicator="line" />} />      <ChartLegend content={<ChartLegendContent />} />    </ChartContainer>  );}​export function Area() {  return (    <ChartContainer config={config} className="aspect-auto h-80">      <AreaChart data={MESSAGES} index="month" tickFormatter={short} stacked aria-label="Messages per month, stacked by channel" />      <ChartTooltip content={<ChartTooltipContent indicator="dashed" />} />    </ChartContainer>  );}​const DELIVERIES = [  { status: "delivered", count: 1240 },  { status: "retrying", count: 86 },  { status: "failed", count: 32 },];​const deliveryConfig = {  delivered: { label: "Delivered", color: "var(--ds-color-chart-series-1)" },  retrying: { label: "Retrying", color: "var(--ds-color-chart-series-2)" },  failed: { label: "Failed", color: "var(--ds-color-chart-series-3)" },} satisfies ChartConfig;​export function Pie() {  return (    <ChartContainer config={deliveryConfig} className="mx-auto aspect-square max-h-72">      <PieChart data={DELIVERIES} index="status" value="count" aria-label="Webhook deliveries by status" />      <ChartTooltip content={<ChartTooltipContent hideLabel />} />      <ChartLegend content={<ChartLegendContent />} />    </ChartContainer>  );}

Source

components/ui/chart.tsx
"use client";​import * as React from "react";​import { cn } from "@/lib/utils";​// Charts drawn in SVG (extrapolated from the Inkbox console, Figma 427:2715: its 12px muted labels, lilac rules, white// floating panels and the purple, pink, blue, teal and green of its states). ChartContainer takes a config that names// and colors each series (series-1 purple, then pink, avatar blue, info teal, success green) and holds the chart, its// tooltip and its legend. Lines draw in from the left and bars rise from the axis once, on mount (700ms and 400ms, the// strong ease-out); with reduced motion they fade in. Hovering or moving with the arrow keys shows the tooltip.​export type ChartConfig = Record<string, { label?: React.ReactNode; icon?: React.ComponentType<{ className?: string }>; color?: string }>;​type Datum = Record<string, string | number>;type Active = { index: number; x: number; y: number } | null;type ChartContextProps = {  config: ChartConfig;  data: Datum[];  setData: (data: Datum[], index: string, keys: string[]) => void;  index: string;  keys: string[];  active: Active;  setActive: (active: Active) => void;  hidden: Set<string>;  toggle: (key: string) => void;};​const ChartContext = React.createContext<ChartContextProps | null>(null);​function useChart() {  const context = React.useContext(ChartContext);  if (!context) throw new Error("useChart must be used within a <ChartContainer />");  return context;}​const SERIES = ["var(--ds-color-chart-series-1)", "var(--ds-color-chart-series-2)", "var(--ds-color-chart-series-3)", "var(--ds-color-chart-series-4)", "var(--ds-color-chart-series-5)"];const colorOf = (config: ChartConfig, key: string, i: number) => `var(--color-${key}, ${config[key]?.color ?? SERIES[i % SERIES.length]})`;​/** Kept for API parity: ChartContainer already sets each series' color as --color-<key> on itself. */function ChartStyle(_props: { id?: string; config: ChartConfig }) {  return null;}​function ChartContainer({ id, className, children, config, ...props }: React.ComponentProps<"div"> & { config: ChartConfig }) {  const [data, setDataState] = React.useState<Datum[]>([]);  const [index, setIndex] = React.useState("");  const [keys, setKeys] = React.useState<string[]>([]);  const [active, setActive] = React.useState<Active>(null);  const [hidden, setHidden] = React.useState<Set<string>>(new Set());  const setData = React.useCallback((d: Datum[], i: string, k: string[]) => {    setDataState(d);    setIndex(i);    setKeys(k);  }, []);  const toggle = React.useCallback((key: string) => setHidden((h) => {    const next = new Set(h);    if (next.has(key)) next.delete(key);    else next.add(key);    return next;  }), []);  const style = Object.fromEntries(Object.entries(config).map(([key, v], i) => [`--color-${key}`, v.color ?? SERIES[i % SERIES.length]])) as React.CSSProperties;  return (    <ChartContext.Provider value={{ config, data, setData, index, keys, active, setActive, hidden, toggle }}>      <div data-slot="chart" data-chart={id} style={style} className={cn("relative flex aspect-video w-full flex-col justify-center font-sans text-caption text-fg-muted", className)} {...props}>        {children}      </div>    </ChartContext.Provider>  );}​function useSize<T extends HTMLElement>() {  const ref = React.useRef<T>(null);  const [size, setSize] = React.useState({ width: 0, height: 0 });  React.useLayoutEffect(() => {    const el = ref.current;    if (!el) return;    const observer = new ResizeObserver(([entry]) => setSize({ width: entry.contentRect.width, height: entry.contentRect.height }));    observer.observe(el);    return () => observer.disconnect();  }, []);  return [ref, size] as const;}​const PAD = { top: 12, right: 12, bottom: 28, left: 12 };​function niceMax(max: number) {  if (max <= 0) return 1;  const step = 10 ** Math.floor(Math.log10(max));  return Math.ceil(max / step) * step;}​/** A smooth path through the points (monotone in x), or straight segments. */function pathOf(points: [number, number][], curve: "monotone" | "linear") {  if (!points.length) return "";  if (curve === "linear" || points.length < 3) return points.map(([x, y], i) => `${i ? "L" : "M"}${x},${y}`).join("");  let d = `M${points[0][0]},${points[0][1]}`;  for (let i = 0; i < points.length - 1; i++) {    const [x0, y0] = points[i - 1] ?? points[i];    const [x1, y1] = points[i];    const [x2, y2] = points[i + 1];    const [x3, y3] = points[i + 2] ?? points[i + 1];    const c1 = [x1 + (x2 - x0) / 6, y1 + (y2 - y0) / 6];    const c2 = [x2 - (x3 - x1) / 6, y2 - (y3 - y1) / 6];    d += `C${c1[0]},${c1[1]} ${c2[0]},${c2[1]} ${x2},${y2}`;  }  return d;}​type CartesianProps = {  data: Datum[];  /** The key of each datum's label on the x axis (month, day). */  index: string;  /** The series to draw, in order; defaults to every key in the config. */  keys?: string[];  showGrid?: boolean;  showXAxis?: boolean;  showYAxis?: boolean;  /** Format the x labels (e.g. the first three letters of a month). */  tickFormatter?: (value: string) => string;  valueFormatter?: (value: number) => string;  className?: string;  "aria-label"?: string;};​function useCartesian(kind: "line" | "area" | "bar", { data, index, keys: keysProp, showYAxis }: CartesianProps, stacked: boolean) {  const chart = useChart();  const keys = React.useMemo(() => keysProp ?? Object.keys(chart.config), [keysProp, chart.config]);  const { setData } = chart;  React.useEffect(() => setData(data, index, keys), [data, index, keys, setData]);  const [ref, size] = useSize<HTMLDivElement>();  const visible = keys.filter((k) => !chart.hidden.has(k));  const values = data.map((d) => (stacked ? visible.reduce((s, k) => s + Number(d[k] ?? 0), 0) : Math.max(0, ...visible.map((k) => Number(d[k] ?? 0)))));  const max = niceMax(Math.max(0, ...values));  const left = PAD.left + (showYAxis ? 32 : 0);  const innerW = Math.max(0, size.width - left - PAD.right);  // Lines and areas keep their first and last points (and labels) clear of the edges.  const inset = kind === "bar" ? 0 : 16;  const innerH = Math.max(0, size.height - PAD.top - PAD.bottom);  const band = data.length ? innerW / data.length : 0;  const xAt = (i: number) => (kind === "bar" ? left + band * i + band / 2 : left + inset + (data.length > 1 ? ((innerW - inset * 2) * i) / (data.length - 1) : innerW / 2 - inset));  const yAt = (v: number) => PAD.top + innerH - (innerH * v) / max;  return { chart, keys, visible, ref, size, max, left, innerW, innerH, band, xAt, yAt };}​function Frame({ kind, props, geometry, children }: { kind: "line" | "area" | "bar"; props: CartesianProps; geometry: ReturnType<typeof useCartesian>; children: React.ReactNode }) {  const { chart, ref, size, max, left, innerW, innerH, band, xAt, yAt } = geometry;  const { data, index, showGrid = true, showXAxis = true, showYAxis = false, tickFormatter = (v: string) => v, valueFormatter = (v: number) => v.toLocaleString() } = props;  const ticks = [0, 0.25, 0.5, 0.75, 1].map((t) => max * t);  const pick = (clientX: number, rect: DOMRect) => {    const x = clientX - rect.left;    let best = 0;    for (let i = 0; i < data.length; i++) if (Math.abs(xAt(i) - x) < Math.abs(xAt(best) - x)) best = i;    return best;  };  const activate = (i: number) => chart.setActive({ index: i, x: xAt(i), y: PAD.top });  const labelStep = Math.max(1, Math.ceil(data.length / Math.max(1, Math.floor(innerW / 56))));  return (    <div ref={ref} className={cn("relative h-full min-h-0 w-full flex-1", props.className)}>      {size.width > 0 && (        <svg          width={size.width}          height={size.height}          role="img"          tabIndex={0}          aria-label={props["aria-label"] ?? "Chart"}          className="block overflow-visible rounded-control outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring"          onPointerMove={(e) => activate(pick(e.clientX, e.currentTarget.getBoundingClientRect()))}          onPointerLeave={() => chart.setActive(null)}          onBlur={() => chart.setActive(null)}          onFocus={() => activate(chart.active?.index ?? 0)}          onKeyDown={(e) => {            if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;            e.preventDefault();            const current = chart.active?.index ?? 0;            activate(Math.min(data.length - 1, Math.max(0, current + (e.key === "ArrowRight" ? 1 : -1))));          }}        >          {showGrid && ticks.map((t) => <line key={t} x1={left} x2={left + innerW} y1={yAt(t)} y2={yAt(t)} className="stroke-line-subtle" strokeWidth={1} strokeDasharray={t === 0 ? undefined : "3 3"} />)}          {showYAxis && ticks.map((t) => (            <text key={t} x={left - 8} y={yAt(t)} dy="0.32em" textAnchor="end" className="fill-fg-muted text-caption">              {valueFormatter(t)}            </text>          ))}          {chart.active && kind !== "bar" && <line x1={xAt(chart.active.index)} x2={xAt(chart.active.index)} y1={PAD.top} y2={PAD.top + innerH} className="stroke-line-field" strokeWidth={1} />}          {chart.active && kind === "bar" && <rect x={xAt(chart.active.index) - band / 2} y={PAD.top} width={band} height={innerH} rx={6} className="fill-subtle" />}          {children}          {showXAxis && data.map((d, i) => (i % labelStep === 0 ? (            <text key={i} x={xAt(i)} y={PAD.top + innerH + 18} textAnchor="middle" className="fill-fg-muted text-caption">              {tickFormatter(String(d[index]))}            </text>          ) : null))}        </svg>      )}    </div>  );}​function LineChart(props: CartesianProps & { curve?: "monotone" | "linear"; dots?: boolean }) {  const g = useCartesian("line", props, false);  const { curve = "monotone", dots = false } = props;  return (    <Frame kind="line" props={props} geometry={g}>      {g.visible.map((key) => {        const i = g.keys.indexOf(key);        const points = props.data.map((d, n) => [g.xAt(n), g.yAt(Number(d[key] ?? 0))] as [number, number]);        return (          <g key={key} data-series={key}>            <path d={pathOf(points, curve)} fill="none" stroke={colorOf(g.chart.config, key, i)} strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" pathLength={1} strokeDasharray="1 1" className="animate-chart-line" />            {(dots || g.chart.active) && points.map(([x, y], n) => (dots || g.chart.active?.index === n ? <circle key={n} cx={x} cy={y} r={g.chart.active?.index === n ? 4 : 3} fill="var(--ds-color-bg-default)" stroke={colorOf(g.chart.config, key, i)} strokeWidth={2} /> : null))}          </g>        );      })}    </Frame>  );}​function AreaChart(props: CartesianProps & { curve?: "monotone" | "linear"; stacked?: boolean }) {  const { curve = "monotone", stacked = false } = props;  const g = useCartesian("area", props, stacked);  const base = props.data.map(() => 0);  const id = React.useId().replace(/:/g, "");  return (    <Frame kind="area" props={props} geometry={g}>      <defs>        {g.visible.map((key) => (          <linearGradient key={key} id={`${id}-${key}`} x1="0" x2="0" y1="0" y2="1">            <stop offset="0%" stopColor={colorOf(g.chart.config, key, g.keys.indexOf(key))} stopOpacity={0.32} />            <stop offset="100%" stopColor={colorOf(g.chart.config, key, g.keys.indexOf(key))} stopOpacity={0.02} />          </linearGradient>        ))}      </defs>      {g.visible.map((key) => {        const i = g.keys.indexOf(key);        const lows = stacked ? [...base] : props.data.map(() => 0);        const highs = props.data.map((d, n) => lows[n] + Number(d[key] ?? 0));        if (stacked) highs.forEach((h, n) => (base[n] = h));        const top = highs.map((v, n) => [g.xAt(n), g.yAt(v)] as [number, number]);        const bottom = lows.map((v, n) => [g.xAt(n), g.yAt(v)] as [number, number]).reverse();        const line = pathOf(top, curve);        const area = `${line}L${bottom.map(([x, y]) => `${x},${y}`).join("L")}Z`;        return (          <g key={key} data-series={key}>            <path d={area} fill={`url(#${id}-${key})`} className="animate-fade-in" />            <path d={line} fill="none" stroke={colorOf(g.chart.config, key, i)} strokeWidth={2} strokeLinecap="round" pathLength={1} strokeDasharray="1 1" className="animate-chart-line" />          </g>        );      })}    </Frame>  );}​function BarChart(props: CartesianProps & { stacked?: boolean; radius?: number }) {  const { stacked = false, radius = 4 } = props;  const g = useCartesian("bar", props, stacked);  const gap = 4;  const groupW = Math.max(0, g.band * 0.72);  const barW = stacked ? groupW : Math.max(2, (groupW - gap * (g.visible.length - 1)) / Math.max(1, g.visible.length));  return (    <Frame kind="bar" props={props} geometry={g}>      {props.data.map((d, n) => {        let acc = 0;        return (          <g key={n}>            {g.visible.map((key, k) => {              const value = Number(d[key] ?? 0);              const x = stacked ? g.xAt(n) - groupW / 2 : g.xAt(n) - groupW / 2 + k * (barW + gap);              const y0 = g.yAt(acc);              const y1 = g.yAt(acc + value);              if (stacked) acc += value;              const top = !stacked || k === g.visible.length - 1;              return (                <rect                  key={key}                  x={x}                  y={y1}                  width={barW}                  height={Math.max(0, y0 - y1)}                  rx={top ? Math.min(radius, barW / 2) : 0}                  fill={colorOf(g.chart.config, key, g.keys.indexOf(key))}                  style={{ transformOrigin: `0 ${g.yAt(0)}px`, animationDelay: `${n * 30}ms` }}                  className="animate-chart-bar"                />              );            })}          </g>        );      })}    </Frame>  );}​function PieChart({ data, index, value, innerRadius = 0.6, className, ...props }: { data: Datum[]; index: string; value: string; innerRadius?: number; className?: string; "aria-label"?: string }) {  const chart = useChart();  const { setData } = chart;  React.useEffect(() => setData(data, index, [value]), [data, index, value, setData]);  const [ref, size] = useSize<HTMLDivElement>();  const shown = data.filter((d) => !chart.hidden.has(String(d[index])));  const total = shown.reduce((s, d) => s + Number(d[value] ?? 0), 0) || 1;  const r = Math.max(0, Math.min(size.width, size.height) / 2 - 8);  const cx = size.width / 2;  const cy = size.height / 2;  let angle = -Math.PI / 2;  return (    <div ref={ref} className={cn("relative h-full min-h-0 w-full flex-1", className)}>      {r > 0 && (        <svg width={size.width} height={size.height} role="img" aria-label={props["aria-label"] ?? "Chart"} className="block animate-pop-in origin-center" onPointerLeave={() => chart.setActive(null)}>          {shown.map((d) => {            const key = String(d[index]);            const n = data.indexOf(d);            const sweep = (Number(d[value] ?? 0) / total) * Math.PI * 2;            const a0 = angle;            const a1 = angle + sweep;            angle = a1;            const ri = r * innerRadius;            const large = sweep > Math.PI ? 1 : 0;            const p = (a: number, rr: number) => `${cx + rr * Math.cos(a)},${cy + rr * Math.sin(a)}`;            const dPath = sweep >= Math.PI * 2 - 1e-6              ? `M${cx - r},${cy}a${r},${r} 0 1,0 ${r * 2},0a${r},${r} 0 1,0 ${-r * 2},0${ri ? `M${cx - ri},${cy}a${ri},${ri} 0 1,1 ${ri * 2},0a${ri},${ri} 0 1,1 ${-ri * 2},0` : ""}`              : `M${p(a0, r)}A${r},${r} 0 ${large} 1 ${p(a1, r)}${ri ? `L${p(a1, ri)}A${ri},${ri} 0 ${large} 0 ${p(a0, ri)}` : `L${cx},${cy}`}Z`;            return (              <path                key={key}                d={dPath}                fillRule="evenodd"                fill={colorOf(chart.config, key, n)}                stroke="var(--ds-color-bg-default)"                strokeWidth={2}                className={cn("transition-opacity duration-150 ease-out", chart.active && chart.active.index !== n && "opacity-60")}                onPointerMove={(e) => {                  const rect = e.currentTarget.ownerSVGElement!.getBoundingClientRect();                  chart.setActive({ index: n, x: e.clientX - rect.left, y: e.clientY - rect.top });                }}              />            );          })}        </svg>      )}    </div>  );}​type TooltipItem = { key: string; name: React.ReactNode; value: number; color: string };​/** Where the tooltip shows: pass `content` (usually <ChartTooltipContent />). It follows the hovered point. */function ChartTooltip({ content, cursor: _cursor }: { content?: React.ReactElement<ChartTooltipContentProps>; cursor?: boolean }) {  const chart = useChart();  if (!chart.active || !chart.data.length) return null;  const datum = chart.data[chart.active.index];  if (!datum) return null;  const pie = chart.keys.length === 1 && !(chart.keys[0] in chart.config) && String(datum[chart.index]) in chart.config;  const payload: TooltipItem[] = pie    ? [{ key: String(datum[chart.index]), name: chart.config[String(datum[chart.index])]?.label ?? String(datum[chart.index]), value: Number(datum[chart.keys[0]] ?? 0), color: colorOf(chart.config, String(datum[chart.index]), chart.active.index) }]    : chart.keys.filter((k) => !chart.hidden.has(k)).map((k, i) => ({ key: k, name: chart.config[k]?.label ?? k, value: Number(datum[k] ?? 0), color: colorOf(chart.config, k, chart.keys.indexOf(k) ?? i) }));  const element = content ?? <ChartTooltipContent />;  return (    <div      data-slot="chart-tooltip"      role="status"      className="pointer-events-none absolute z-10 animate-fade-in"      style={{ left: chart.active.x, top: chart.active.y, transform: `translate(${chart.active.x > 160 ? "calc(-100% - 12px)" : "12px"}, 0)` }}    >      {React.cloneElement(element, { payload, label: pie ? undefined : String(datum[chart.index]) })}    </div>  );}​type ChartTooltipContentProps = React.ComponentProps<"div"> & {  payload?: TooltipItem[];  label?: string;  indicator?: "dot" | "line" | "dashed";  hideLabel?: boolean;  hideIndicator?: boolean;  labelFormatter?: (label: string) => React.ReactNode;  formatter?: (value: number, name: React.ReactNode, key: string) => React.ReactNode;};​/** The tooltip's panel: white, 8px corners, the floating shadow, the label in 13px medium ink, a row per series. */function ChartTooltipContent({ payload = [], label, indicator = "dot", hideLabel = false, hideIndicator = false, labelFormatter, formatter, className, ...props }: ChartTooltipContentProps) {  return (    <div data-slot="chart-tooltip-content" className={cn("grid min-w-32 gap-1.5 rounded-control bg-surface px-3 py-2 text-caption leading-tight text-fg shadow-floating", className)} {...props}>      {!hideLabel && label !== undefined && <p className="text-body-sm font-medium text-fg">{labelFormatter ? labelFormatter(label) : label}</p>}      <div className="grid gap-1">        {payload.map((item) => (          <div key={item.key} className="flex items-center gap-2">            {!hideIndicator && (              <span                aria-hidden                className={cn("shrink-0", indicator === "dot" ? "size-2.5 rounded-xs" : "h-3 w-1 rounded-xs", indicator === "dashed" && "border border-dashed bg-transparent")}                style={indicator === "dashed" ? { borderColor: item.color } : { background: item.color }}              />            )}            {formatter ? formatter(item.value, item.name, item.key) : (              <>                <span className="text-fg-muted">{item.name}</span>                <span className="ml-auto pl-3 font-medium text-fg tabular-nums">{item.value.toLocaleString()}</span>              </>            )}          </div>        ))}      </div>    </div>  );}​/** Where the legend shows: pass `content` (usually <ChartLegendContent />). */function ChartLegend({ content, verticalAlign = "bottom" }: { content?: React.ReactElement<React.ComponentProps<typeof ChartLegendContent>>; verticalAlign?: "top" | "bottom" }) {  const element = content ?? <ChartLegendContent />;  return <div className={cn(verticalAlign === "top" ? "order-first pb-3" : "pt-3")}>{element}</div>;}​/** A row of 12px muted labels with their color keys; each toggles its series when `interactive`. */function ChartLegendContent({ className, hideIcon = false, interactive = true, ...props }: React.ComponentProps<"div"> & { hideIcon?: boolean; interactive?: boolean }) {  const chart = useChart();  const keys = chart.keys.length === 1 && !(chart.keys[0] in chart.config) ? chart.data.map((d) => String(d[chart.index])) : chart.keys;  return (    <div data-slot="chart-legend-content" className={cn("flex flex-wrap items-center justify-center gap-x-4 gap-y-1.5", className)} {...props}>      {keys.map((key, i) => {        const item = chart.config[key];        const Icon = item?.icon;        const off = chart.hidden.has(key);        const body = (          <>            {Icon && !hideIcon ? <Icon className="size-3.5" /> : <span aria-hidden className="size-2.5 rounded-xs" style={{ background: colorOf(chart.config, key, i) }} />}            {item?.label ?? key}          </>        );        return interactive ? (          <button key={key} type="button" aria-pressed={!off} onClick={() => chart.toggle(key)} className={cn("inline-flex items-center gap-1.5 rounded-xs text-caption leading-tight text-fg-secondary outline-none transition-opacity duration-150 ease-out focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring motion-reduce:transition-none", off && "opacity-40")}>            {body}          </button>        ) : (          <span key={key} className="inline-flex items-center gap-1.5 text-caption leading-tight text-fg-secondary">            {body}          </span>        );      })}    </div>  );}​export { AreaChart, BarChart, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, LineChart, PieChart, useChart };

Reference: Extrapolated from the Inkbox console (Figma 427:2715): muted labels, lilac rules, floating panels and the status colors.