Components

Charts

The chart library in @repo/ui is a composable wrapper around recharts that applies the design token vocabulary consistently. All chart components are dark-theme compatible and use semantic CSS custom properties — never hardcoded colors.

Peer dependency: Add recharts >=2.0.0 to the consuming app's dependencies. @repo/ui lists it as a peer dependency only — it does not bundle recharts.

Architecture

The system is split into three layers. The core layer provides a shared context, Card wrapper, and slot components. The wrapper layer adds recharts-specific chart types. A standalone component handles the specialized stacked-bar log pattern.

FileImport pathContents
chart.tsx@repo/ui/components/chartCore context, ChartCard, ChartHeader, ChartContent, ChartFooter, ChartTooltip, ChartConfig type, useChart hook
bar-chart.tsx@repo/ui/components/bar-chartBarChartCard — recharts BarChart wrapper
line-chart.tsx@repo/ui/components/line-chartLineChartCard — recharts LineChart / AreaChart wrapper
logs-bar-chart.tsx@repo/ui/components/logs-bar-chartLogsBarChart — compact stacked status bars

ChartConfig

Every chart that uses ChartTooltip receives a config prop of type ChartConfig. The config maps each data key to a display label, an optional fixed color, and an optional theme-aware color map.

import type { ChartConfig } from '@repo/ui/components/chart'

// Fixed color (same in light and dark)
const config: ChartConfig = {
  count: { label: 'Tasks', color: 'var(--brand-default)' },
}

// Theme-aware color (resolve automatically based on data-theme)
const config: ChartConfig = {
  revenue: {
    label: 'Revenue',
    theme: {
      light: 'hsl(152.9deg 60% 52.9%)',
      dark:  'hsl(153.1deg 60.2% 52.7%)',
    },
  },
}

When using theme colors, the current theme class on the root element determines which value is used. Use CSS custom properties via var(--brand-default) for colors that already adapt automatically through the token system — this is the preferred approach and avoids needing the theme key at all.

Core components

ChartCard

The root wrapper. Provides ChartContext with isLoading, isDisabled, and config. Renders as a Card from @repo/ui/components/card.

PropTypeDefaultDescription
isLoadingbooleanfalsePropagates to ChartContent which renders a Skeleton instead of children
isDisabledbooleanfalseApplies opacity-60 pointer-events-none to the whole card
configChartConfig{}Passed to ChartTooltip automatically via context

ChartHeader

A two-column row inside the card: a left column holds the title and optional metric summary, a right column holds an optional action element (e.g. a refresh button or time-range selector).

PropTypeDescription
titleReactNodeChart title rendered as a small semibold label
metricReactNodeSecondary line below the title — typically a summary stat or date range
actionReactNodeRight-aligned slot for buttons or controls

ChartContent

The main chart area. Reads isLoading from context: if loading, renders a Skeleton at the specified height. If isEmpty is true, renders a centered empty state message. Otherwise renders children in a sized container.

PropTypeDefaultDescription
heightnumber224Pixel height of the chart area (and the Skeleton when loading)
isEmptybooleanfalseShows centered empty state instead of children
emptyMessagestring'No data available'Text shown in empty state

A below-chart slot for captions, data source notes, or legend summaries. Renders as a CardFooter with text-foreground-lighter text-xs.

ChartTooltip

A recharts Tooltip with consistent dark-surface styling. Reads config from ChartContext to resolve labels and colors for each data key. Can be placed inside any recharts chart.

PropTypeDescription
labelFormatter(label: string) => stringTransform the x-axis label shown at the top of the tooltip
valueFormatter(value: number, name: string) => stringFormat numeric values (e.g. add units, currency symbols)
configChartConfigOverride the context config for this tooltip instance

useChart

Hook that reads the nearest ChartContext. Use inside custom recharts content renderers or child components to access isLoading, isDisabled, and config without prop drilling.

import { useChart } from '@repo/ui/components/chart'

function CustomLegend() {
  const { config } = useChart()
  return (
    <div>
      {Object.entries(config).map(([key, { label, color }]) => (
        <span key={key} style={{ color }}>{label}</span>
      ))}
    </div>
  )
}

Bar chart

BarChartCard

A complete card with ChartCard, optional ChartHeader, a recharts BarChart inside ChartContent, and optional ChartFooter. Supports vertical bars (default) and horizontal bars.

import { BarChartCard } from '@repo/ui/components/bar-chart'
import type { ChartConfig } from '@repo/ui/components/chart'

const config: ChartConfig = {
  count: { label: 'Tasks completed', color: 'var(--brand-default)' },
}

const data = [
  { week: 'Feb 3', count: 4 },
  { week: 'Feb 10', count: 7 },
  { week: 'Feb 17', count: 5 },
  { week: 'Feb 24', count: 11 },
]

<BarChartCard
  title="Weekly velocity"
  data={data}
  dataKey="count"
  config={config}
  categoryKey="week"
  height={224}
/>
PropTypeDefaultDescription
dataDataPoint[]recharts data array
dataKeystring | string[]Key(s) to render as bars. Pass an array for grouped bars.
configChartConfigLabel and color mapping for each data key
categoryKeystring'name'Key used for the category axis
layout'horizontal' | 'vertical''horizontal'horizontal = vertical bars (standard); vertical = horizontal bars (agent breakdown, rankings)
colorKeystringProperty on each data point to use as per-bar fill (overrides config color)
showGridbooleanfalseRender a CartesianGrid using --border-default
showYAxisbooleantrueShow the value axis
showXAxisbooleantrueShow the category axis
heightnumber224Chart area height in px
syncIdstringrecharts syncId for linked tooltips across charts
isLoadingbooleanfalseShows Skeleton in place of chart
title, metric, action, footerReactNodeForwarded to ChartHeader / ChartFooter; omitting them suppresses those sections entirely

Horizontal bars (rankings / agent breakdown)

<BarChartCard
  title="Completions by agent"
  data={agentData}
  dataKey="count"
  config={config}
  categoryKey="label"
  layout="vertical"
  colorKey="color"   // each data point has a .color field
  showYAxis={true}
  height={224}
/>

Grouped bars

const config: ChartConfig = {
  done:    { label: 'Done',    color: 'var(--brand-default)' },
  blocked: { label: 'Blocked', color: 'var(--destructive-default)' },
}

<BarChartCard
  data={data}
  dataKey={['done', 'blocked']}
  config={config}
  categoryKey="week"
/>

Line chart

LineChartCard

Wraps recharts LineChart (or AreaChart when showArea is true). Supports multiple lines via array dataKey.

import { LineChartCard } from '@repo/ui/components/line-chart'

const config: ChartConfig = {
  tokens: { label: 'Tokens', color: 'var(--brand-default)' },
}

<LineChartCard
  title="Token usage over time"
  data={data}
  dataKey="tokens"
  config={config}
  categoryKey="week"
  showArea
  height={224}
/>
PropTypeDefaultDescription
dataKeystring | string[]Key(s) to render as lines
showAreabooleanfalseSwitches to AreaChart with a gradient fill below each line
showDotsbooleanfalseRender static dots on every data point (hover dots are always shown)
showGridbooleanfalseShow horizontal grid lines
showYAxisbooleantrueShow the value (Y) axis
showXAxisbooleantrueShow the category (X) axis
syncIdstringrecharts syncId for linked charts
height, isLoading, title, metric, action, footerSame as BarChartCard

Multi-line with area fills

const config: ChartConfig = {
  input_tokens:  { label: 'Input',  color: 'var(--brand-default)' },
  output_tokens: { label: 'Output', color: '#a78bfa' },
}

<LineChartCard
  data={data}
  dataKey={['input_tokens', 'output_tokens']}
  config={config}
  categoryKey="date"
  showArea
  showGrid
/>

Logs bar chart

LogsBarChart

A compact stacked bar chart for visualising status counts over time. Inspired by the Supabase dashboard log viewer. Status colors are fixed semantic constants: ok, warning, error.

Unlike the other chart components, LogsBarChart is a standalone component — it does not use ChartCard internally. Embed it inside any container with the height you need.

import { LogsBarChart } from '@repo/ui/components/logs-bar-chart'
import type { LogsDataPoint } from '@repo/ui/components/logs-bar-chart'

const data: LogsDataPoint[] = [
  { timestamp: '2026-02-28T00:00:00Z', ok_count: 142, error_count: 3, warning_count: 8 },
  { timestamp: '2026-02-28T01:00:00Z', ok_count: 98,  error_count: 0, warning_count: 2 },
  { timestamp: '2026-02-28T02:00:00Z', ok_count: 203, error_count: 11, warning_count: 4 },
]

// Compact (default h-24, no axis labels)
<LogsBarChart data={data} />

// With timestamps on the X axis
<LogsBarChart data={data} height={96} showTimestamps />

// Inside a card header
<Card>
  <CardHeader>
    <CardTitle>API Health — Last 24h</CardTitle>
  </CardHeader>
  <CardContent className="px-4 pb-4 pt-0">
    <LogsBarChart data={data} />
  </CardContent>
</Card>
PropTypeDefaultDescription
dataLogsDataPoint[]Array of { timestamp, ok_count, error_count, warning_count }
heightnumber96Height in px
showTimestampsbooleanfalseRender an X axis with locale-formatted HH:MM labels
isLoadingbooleanfalseRenders a Skeleton at the given height

Composing a custom chart

Use the bare Chart provider (not ChartCard) when you need the context and ChartTooltip without the Card wrapper — for example, embedding a chart inside an existing panel.

import {
  Chart,
  ChartTooltip,
  type ChartConfig,
} from '@repo/ui/components/chart'
import { ResponsiveContainer, LineChart, Line } from 'recharts'

const config: ChartConfig = {
  value: { label: 'Score', color: 'var(--brand-default)' },
}

<Chart config={config} isLoading={loading} className="h-40 w-full">
  <ResponsiveContainer width="100%" height="100%">
    <LineChart data={data}>
      <ChartTooltip />
      <Line type="monotone" dataKey="value" stroke="var(--brand-default)" strokeWidth={2} dot={false} />
    </LineChart>
  </ResponsiveContainer>
</Chart>

Styling reference

All chart components use semantic tokens. The axis tick color is var(--foreground-lighter) at fontSize 11. Grid lines use var(--border-default). Tooltip background is bg-surface-200 with a border-border stroke and rounded-lg corners.

ElementToken / class
Axis ticksvar(--foreground-lighter), fontSize: 11
Axis lines / tick linesHidden (axisLine=false, tickLine=false)
Gridvar(--border-default), dashed 3 3
Tooltip backgroundbg-surface-200
Tooltip borderborder-border rounded-lg
Bar radius (vertical)[4, 4, 0, 0] top corners only
Bar radius (horizontal)[0, 4, 4, 0] right corners only
Line stroke width2
Hover dot radius4
Primary chart colorvar(--brand-default)