Themed charts on Recharts: line, area, bar and donut in one frame with a table view, plus sparklines and a stat strip.
The chart layer lives in components/charts. Every chart mounts in ChartFrame: a caption, a legend, the plot and a Chart or Table toggle whose table is the same data for anyone who cannot read the picture. Loading, refreshing, empty and error states are built in (status).
Charts carry no hex. Every mark reads a custom property (--chart-1 to --chart-6, --chart-seq-*, --chart-good and friends) declared in tokens.css, and chart chrome (grid, axes, ticks) derives from the global tokens, so a preset, dark mode or a Rime Studio theme re-themes them with no client code. Tooltips glide between points (TOOLTIP_MOTION: Recharts' 400ms ease, off under reduced motion) and use the compact shadcn layout on the popover glass, with dot, line or dashed indicators.
The high-level charts take series (Rime's list) or config (a shadcn ChartConfig), and expose --color-<key> per series on the root. For anything custom, the shadcn-compatible primitives are here too: ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent and type ChartConfig. A shadcn chart example works with its Tailwind classes removed and ChartContainer given a height. The full gallery, with every tooltip variant, is at /charts.
Series take their colour in a fixed order (the colour-blind safety mechanism): never re-rank on filter, and past six series fold the rest into "Other" (foldSeries). summary is required: one or two sentences a screen reader hears in place of the plot.
StatStrip is the summary-card pattern for figures: one surface, hairline cells, each with a value, a delta in words and an optional Sparkline.
Installation
npx shadcn@latest add KezLahd/rime/chartsAlso installs: base, button, icon, skeleton, tabs.
Usage
import { AreaChart, BarChart, ChartContainer, ChartTooltip, ChartTooltipContent, DonutChart, LineChart, Sparkline, StatStrip, type ChartConfig } from "@/components/charts";
<LineChart
title="Orders per month"
summary="Online orders rose from 182 in January to 344 in August."
data={rows}
x="month"
series={[{ key: "online", label: "Online" }]}
/>Examples
Area, two series
Online orders rose every month, from 182 in January to 344 in August. Retail held between 118 and 161.
- Online
- Retail
Line with a reference
Online orders rose from 182 in January to 344 in August, against a target of 300.
Bars, labelled
North leads with 48,200 in revenue; Central is lowest at 18,900.
Donut
Of 40 projects, 22 are on track, 9 in review, 6 drafts and 3 blocked.
Stat strip with sparklines
- Orders
- 505
- +32vs last monthMonthly, last 8 months: 302 to 505
- Revenue
- 165,400
- +12,800vs last month
- Median turnaround
- 1.8days
- −0.3vs last month
- Open invoices
- 14
- 3 overdue
The shadcn primitives
API
Generated from the component's TypeScript types and JSDoc (scripts/gen-ui-registry.mjs), so it always matches the source.
TrendChart
| Prop | Type | Default | Description |
|---|---|---|---|
| titleRequired | ReactNode | ||
| subtitle | ReactNode | ||
| summaryRequired | string | One or two sentences a screen reader hears in place of the picture: what is plotted and the headline ("Weekly orders rose from 8 to 32"). Also set as the SVG's <desc>. | |
| hideTitle | boolean | Keeps the title for assistive tech but hides it, when a Group heading already says it. | |
| status | ChartStatus | ||
| emptyTitle | ReactNode | ||
| emptyDescription | ReactNode | What to do next, or what will fill this. A bare "No data" is not an empty state. | |
| emptyAction | ReactNode | ||
| errorTitle | ReactNode | ||
| errorDescription | ReactNode | ||
| onRetry | () => void | ||
| actions | ReactNode | Controls placed beside the table toggle (a segmented control, a menu). | |
| height | number | 248 | Plot height in px, x-axis band included. |
| className | string | ||
| dataRequired | ReadonlyArray<ChartRow> | ||
| xRequired | string | Field holding the x value (an ISO date for time series). | |
| formatX | (value: string) => string | String | Axis tick: short ("23 Nov"). |
| formatXLong | (value: string) => string | Tooltip header and table rows: in full ("Week of 23 Nov 2026"). | |
| formatValue | (value: number) => string | formatNumber | |
| xLabel | string | "Week" | Table header for the x column. |
| endLabels | boolean | true | Value and name at each line's end. On by default for up to four series; switched off automatically where the ends would collide. |
| reference | { value: number; label: string } | One horizontal limit on the same axis (program places). Never a second axis. | |
| tooltipTag | string | Tooltip header tag, e.g. "Cumulative". | |
| seriesRequired | ReadonlyArray<ChartSeries> | never | ||
| config | never | ChartConfig |
AreaChart
| Prop | Type | Default | Description |
|---|---|---|---|
| titleRequired | ReactNode | ||
| subtitle | ReactNode | ||
| summaryRequired | string | One or two sentences a screen reader hears in place of the picture: what is plotted and the headline ("Weekly orders rose from 8 to 32"). Also set as the SVG's <desc>. | |
| hideTitle | boolean | Keeps the title for assistive tech but hides it, when a Group heading already says it. | |
| status | ChartStatus | ||
| emptyTitle | ReactNode | ||
| emptyDescription | ReactNode | What to do next, or what will fill this. A bare "No data" is not an empty state. | |
| emptyAction | ReactNode | ||
| errorTitle | ReactNode | ||
| errorDescription | ReactNode | ||
| onRetry | () => void | ||
| actions | ReactNode | Controls placed beside the table toggle (a segmented control, a menu). | |
| height | number | Plot height in px, x-axis band included. | |
| className | string | ||
| dataRequired | ReadonlyArray<ChartRow> | ||
| xRequired | string | Field holding the x value (an ISO date for time series). | |
| formatX | (value: string) => string | Axis tick: short ("23 Nov"). | |
| formatXLong | (value: string) => string | Tooltip header and table rows: in full ("Week of 23 Nov 2026"). | |
| formatValue | (value: number) => string | ||
| xLabel | string | Table header for the x column. | |
| endLabels | boolean | Value and name at each line's end. On by default for up to four series; switched off automatically where the ends would collide. | |
| reference | { value: number; label: string } | One horizontal limit on the same axis (program places). Never a second axis. | |
| tooltipTag | string | Tooltip header tag, e.g. "Cumulative". | |
| seriesRequired | ReadonlyArray<ChartSeries> | never | ||
| config | never | ChartConfig | ||
| stacked | boolean | Parts of a whole over time. Off for a single running total. |
BarChart
| Prop | Type | Default | Description |
|---|---|---|---|
| titleRequired | ReactNode | ||
| subtitle | ReactNode | ||
| summaryRequired | string | One or two sentences a screen reader hears in place of the picture: what is plotted and the headline ("Weekly orders rose from 8 to 32"). Also set as the SVG's <desc>. | |
| hideTitle | boolean | Keeps the title for assistive tech but hides it, when a Group heading already says it. | |
| status | ChartStatus | ||
| emptyTitle | ReactNode | ||
| emptyDescription | ReactNode | What to do next, or what will fill this. A bare "No data" is not an empty state. | |
| emptyAction | ReactNode | ||
| errorTitle | ReactNode | ||
| errorDescription | ReactNode | ||
| onRetry | () => void | ||
| actions | ReactNode | Controls placed beside the table toggle (a segmented control, a menu). | |
| height | number | Plot height in px, x-axis band included. | |
| className | string | ||
| dataRequired | ReadonlyArray<ChartRow> | ||
| categoryRequired | string | Field holding the category (region, team member, week, stage). | |
| orientation | "columns" | "bars" | "columns" | "bars" | "columns" | columns = vertical bars, for time and short labels. bars = horizontal, for many or long-named categories (regions, team members). |
| stacked | boolean | false | Parts of a whole per category. |
| categoryColors | ReadonlyArray<string> | One colour per category, for ordered categories on a single series (a funnel's stages, from ordinalColors). Nominal categories never get this: one series is one colour. | |
| formatCategory | (value: string) => string | String | |
| formatCategoryLong | (value: string) => string | ||
| formatValue | (value: number) => string | formatNumber | |
| categoryLabel | string | "Category" | |
| valueLabels | boolean | Value at each bar's end. Default: on for a single unstacked series. | |
| max | number | Fixed value-axis top, e.g. 5 for places per team member. | |
| categoryWidth | number | 150 | Width reserved for horizontal-bar category labels. |
| tooltipTag | string | ||
| seriesRequired | ReadonlyArray<ChartSeries> | never | ||
| config | never | ChartConfig |
DonutChart
| Prop | Type | Default | Description |
|---|---|---|---|
| summaryRequired | string | One or two sentences a screen reader hears in place of the picture: what is plotted and the headline ("Weekly orders rose from 8 to 32"). Also set as the SVG's <desc>. | |
| titleRequired | ReactNode | ||
| className | string | ||
| subtitle | ReactNode | ||
| hideTitle | boolean | Keeps the title for assistive tech but hides it, when a Group heading already says it. | |
| status | ChartStatus | ||
| emptyTitle | ReactNode | ||
| emptyDescription | ReactNode | What to do next, or what will fill this. A bare "No data" is not an empty state. | |
| emptyAction | ReactNode | ||
| errorTitle | ReactNode | ||
| errorDescription | ReactNode | ||
| onRetry | () => void | ||
| actions | ReactNode | Controls placed beside the table toggle (a segmented control, a menu). | |
| dataRequired | ReadonlyArray<DonutDatum> | ||
| centreLabelRequired | string | Centre figure label, e.g. "Customers". The figure is the total. | |
| centreValue | string | Override the centre figure (defaults to the sum). | |
| formatValue | (value: number) => string | formatNumber | |
| size | number | 168 | Ring diameter in px. |
| categoryLabel | string | "Group" |
StatStrip
| Prop | Type | Default | Description |
|---|---|---|---|
| statsRequired | ReadonlyArray<Stat> | ||
| loading | boolean | ||
| className | string | ||
| aria-label | string |
Sparkline
| Prop | Type | Default | Description |
|---|---|---|---|
| valuesRequired | ReadonlyArray<number> | ||
| width | number | 96 | |
| height | number | 28 | |
| emphasiseLast | boolean | true | The history in the de-emphasis hue, the current period (last segment and end dot) in the accent. Set false for an all-accent line. |
| color | string | "var(--chart-1)" | Accent colour, a var() from palette.ts. |
| zeroBased | boolean | true | Include zero in the scale. On for counts, off for rates that never near it. |
| className | string |
ChartContainer
| Prop | Type | Default | Description |
|---|---|---|---|
| configRequired | ChartConfig | ||
| childrenRequired | ComponentProps<typeof ResponsiveContainer>["children"] | One Recharts chart (AreaChart, BarChart...). | |
| initialDimension | { width: number; height: number } | { width: 320, height: 200 } | Size used before the first measure, so SSR and first paint are not 0 by 0. |
Also accepts the native attributes of AriaAttributes, DOMAttributes, HTMLAttributes.
ChartTooltipContent
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | ||
| indicator | Indicator | "dot" | dot (default), line or dashed key beside each row. |
| hideLabel | boolean | false | |
| hideIndicator | boolean | false | |
| labelKey | string | Config key to read the label from, when the label is not the x value. | |
| nameKey | string | Row name from this field of the datum (pies: "browser"). | |
| color | string | Forces every indicator to one colour. | |
| labelFormatter | (label: ReactNode, payload: ReadonlyArray<TooltipPayloadEntry>) => ReactNode | Header text. Receives the resolved label and the payload. | |
| formatter | ( value: TooltipPayloadEntry["value"], name: TooltipPayloadEntry["name"], item: TooltipPayloadEntry, index: number, datum: unknown, ) => ReactNode | Replaces a row's whole body. | |
| valueFormatter | (value: number) => string | formatNumber | Formats each numeric value (kit default: formatNumber). |
| tag | string | Small uppercase tag beside the label ("Cumulative"). Kit extra. | |
| total | boolean | string | Adds a total row: stacked charts. Kit extra. |
Also accepts the native attributes of DOM attributes, Props.
ChartLegendContent
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | ||
| hideIcon | boolean | false | |
| nameKey | string | ||
| payload | ReadonlyArray<LegendPayload> | ||
| verticalAlign | "top" | "middle" | "bottom" | "bottom" |
Theme tokens
Per-component tokens are undeclared by default, so the default shown is the fallback the CSS uses. Set one at :root (or in theme.css) to restyle every instance. Live values are read from this page, so they follow the Rime Studio.
| Token | Default | Live value |
|---|---|---|
| --chart-axis | var(--line) | … |
| --chart-band | var(--wash-hover) | … |
| --chart-cursor | var(--line) | … |
| --chart-grid | var(--line-soft) | … |
| --chart-tick | var(--ink-muted) | … |
| --popover-blur | none | … |
| --popover-border | transparent | … |
| --popover-shadow | var(--shadow-float) | … |
| --popover-surface | var(--surface-solid) | … |
| --r-circle | 50% | … |
| --r-flat | 2px | … |
| --stat-count | 4 | … |
System tokens it reads
| Token | Default | Live value |
|---|---|---|
| --brand | tokens.css | … |
| --danger-fill | tokens.css | … |
| --danger-ink | tokens.css | … |
| --dur | tokens.css | … |
| --dur-slow | tokens.css | … |
| --ease | tokens.css | … |
| --focus-ring | tokens.css | … |
| --font-body | tokens.css | … |
| --glass-fill-nested | tokens.css | … |
| --indicator | tokens.css | … |
| --ink-body | tokens.css | … |
| --ink-brand | tokens.css | … |
| --ink-disabled | tokens.css | … |
| --ink-heading | tokens.css | … |
| --ink-muted | tokens.css | … |
| --ink-secondary | tokens.css | … |
| --line | tokens.css | … |
| --line-glass | tokens.css | … |
| --line-soft | tokens.css | … |
| --r-2xs | tokens.css | … |
| --r-md | tokens.css | … |
| --r-sm | tokens.css | … |
| --r-xs | tokens.css | … |
| --rgb-brand | tokens.css | … |
| --rgb-brand-deep | tokens.css | … |
| --shadow-float | tokens.css | … |
| --success-ink | tokens.css | … |
| --surface-solid | tokens.css | … |
| --surface-sunken | tokens.css | … |
| --table-cell-pad | tokens.css | … |
| --table-head-fill | tokens.css | … |
| --table-head-ink | tokens.css | … |
| --table-head-pad | tokens.css | … |
| --table-head-rule | tokens.css | … |
| --table-row-hover | tokens.css | … |
| --table-row-line | tokens.css | … |
| --text-caption | tokens.css | … |
| --text-micro | tokens.css | … |
| --text-small | tokens.css | … |
| --tracking-label | tokens.css | … |
| --wash-active | tokens.css | … |
| --wash-hover | tokens.css | … |
| --wash-subtle | tokens.css | … |
Accessibility
- Every chart is a <figure> labelled by its title and described by summary, which is also the SVG's <desc>.
- The Chart or Table toggle shows the same data as a real table with a caption, for screen readers and for anyone comparing values.
- Colour is never the only key: legends name each series, end labels sit on lines, status hues always come with a label.
- The categorical order is the colour-blind safety mechanism; keep it.
Do and don't
Do
- Write summary as the headline: what changed and by how much.
- Use StatStrip for headline figures: one surface, hairline cells, never a grid of separate cards.
- Pass status="loading" while data loads so the skeleton matches the chart's shape.
Don't
- Don't hard-code a hex on a mark; set --chart-* tokens instead.
- Don't use more than six series; fold the rest into Other.
- Don't use status colours (good, warning, danger) as series identity.
For AI agents
This page as markdown, rendered from the same registry entry. Point an agent at it (or add .md to this page's URL), or at /llms.txt for the index.
Open chart.md