Chart
Anatomy
Section titled “Anatomy”<Chart> <DonutChart /> <Legend> <LegendItem /> <LegendItem /> </Legend></Chart>Chart: Wraps and provides shared context (colors, currently hovered item, etc.) to child components. Not required if using a display-only chart without user interactions. ForBarChartandLineChart, passcategories(the series keys) so theChart,Legend, andChartTooltipshare one color per series.DonutChart: Displays data as parts of a whole, with an optional center value and label.BarChart: Compares categories, stacks composition, and renders pre-binned histograms.LineChart: Shows one or more series as a trend over time.ChartTooltip: A tokenized hover tooltip used byBarChartandLineChart.Legend: Displays a list of labels and values related to the chart.
import { Chart, DonutChart, Legend, LegendItem } from "stylus-ui/Chart";
const data = [ { name: "Notion", amount: 281 }, { name: "VS Code", amount: 142 }, { name: "Slack", amount: 50 },];
export default () => ( <Chart className="flex items-center gap-8" data={data} category="name"> <DonutChart value="amount" /> <Legend className="w-48"> {data.map((item) => ( <LegendItem key={item.name} dataKey={item.name} label={item.name} value={item.amount} /> ))} </Legend> </Chart>);Donut chart
Section titled “Donut chart”For the simplest display, you can use DonutChart standalone, without the Chart wrapper.
import { DonutChart } from "stylus-ui/Chart";
const data = [ { name: "Notion", amount: 281 }, { name: "VS Code", amount: 142 }, { name: "Slack", amount: 50 },];
export default () => ( <DonutChart category="name" value="amount" data={data} className="size-16" />);Colors
Section titled “Colors”Each slice takes a color from the accent palette positionally. The available colors are accent-1, accent-2, accent-3, accent-4, accent-5, and accent-neutral (reserved for the "Other" / long-tail bucket).
Specific colors only
Section titled “Specific colors only”By default, the chart cycles through the accent series in order. To pin or reorder, pass an array of accent keys to the colors prop:
import { DonutChart } from "stylus-ui/Chart";
const data = [ { name: "Primary", value: 40 }, { name: "Secondary", value: 30 }, { name: "Tertiary", value: 20 }, { name: "Other", value: 10 },];
export default () => ( <DonutChart category="name" value="value" data={data} colors={["accent-1", "accent-3", "accent-5", "accent-neutral"]} />);Display value
Section titled “Display value”Display a sum of all chart segments in the center by setting showValue to true.
import { DonutChart } from "stylus-ui/Chart";import { formatIntlNumber } from "scribe-web-shared/functions";
const data = [ { name: "Notion", amount: 2814 }, { name: "VS Code", amount: 142 }, { name: "Slack", amount: 503 },];
export default () => ( <DonutChart category="name" value="amount" data={data} showValue />);Long values will automaically shrink to fit.
import { DonutChart } from "stylus-ui/Chart";import { formatIntlNumber } from "scribe-web-shared/functions";
const data = [ { name: "Notion", revenue: 4500600 }, { name: "VS Code", revenue: 3200800 }, { name: "Slack", revenue: 1800900 },];
export default () => ( <DonutChart category="name" value="revenue" data={data} showValue />);Formatting values
Section titled “Formatting values”Use the valueFormatter prop to format the display value. For example, you can use the formatIntlNumber helper to display locale-appropriate thousands separators for long numbers.
import { DonutChart } from "stylus-ui/Chart";import { formatIntlNumber } from "scribe-web-shared/functions";
const data = [ { name: "Notion", amount: 2814 }, { name: "VS Code", amount: 142 }, { name: "Slack", amount: 503 },];
export default () => ( <DonutChart category="name" value="amount" data={data} showValue valueFormatter={(v) => formatIntlNumber(v)} />);With label
Section titled “With label”To display text above the value, set label.
import { DonutChart } from "stylus-ui/Chart";import { formatIntlNumber } from "scribe-web-shared/functions";
const data = [ { name: "Notion", hours: 281 }, { name: "VS Code", hours: 142 }, { name: "Slack", hours: 50 },];
export default () => ( <DonutChart category="name" value="hours" data={data} showValue valueFormatter={(v) => `${formatIntlNumber(v)} hrs`} label="Yearly average" />);No data
Section titled “No data”When there's no data or all values are zero, the chart displays a full ring with a - in the center:
import { DonutChart } from "stylus-ui/Chart";
export default () => ( <DonutChart category="name" value="amount" data={[]} showValue label="Yearly average" />);With legend
Section titled “With legend”Combine the DonutChart with the Legend component for interactive data visualization. The Chart wrapper automatically synchronizes hover and click states between components with zero boilerplate:
import React from "react";import { Chart, DonutChart, Legend, LegendItem } from "stylus-ui/Chart";
const data = [ { name: "Notion", amount: 281 }, { name: "VS Code", amount: 142 }, { name: "GitHub", amount: 87 }, { name: "Slack", amount: 50 }, { name: "Figma", amount: 43 },];
export default () => { const total = data.reduce((sum, item) => sum + item.amount, 0);
return ( <Chart className="flex items-center gap-8" data={data} category="name"> <DonutChart value="amount" label="Yearly average" valueFormatter={(v) => `${v} hrs`} showValue /> <Legend> {data.map((item) => ( <LegendItem key={item.name} dataKey={item.name} label={item.name} value={`${item.amount} hrs (${((item.amount / total) * 100).toFixed(1)}%)`} /> ))} </Legend> </Chart> );};With click interactions
Section titled “With click interactions”Add click handling to toggle selection by providing an onClick callback to the Chart component:
import React from "react";import { Chart, DonutChart, Legend, LegendItem } from "stylus-ui/Chart";
const data = [ { name: "Notion", amount: 281 }, { name: "VS Code", amount: 142 }, { name: "GitHub", amount: 87 }, { name: "Slack", amount: 50 }, { name: "Figma", amount: 43 },];
export default () => { const [selected, setSelected] = React.useState(null); const total = data.reduce((sum, item) => sum + item.amount, 0);
return ( <Chart className="flex items-center gap-8" data={data} category="name" selectedDataKey={selected} onClick={setSelected} > <DonutChart value="amount" label="Yearly average" valueFormatter={(v) => `${v} hrs`} showValue /> <Legend> {data.map((item) => ( <LegendItem key={item.name} dataKey={item.name} label={item.name} value={`${item.amount} hrs (${((item.amount / total) * 100).toFixed(1)}%)`} /> ))} </Legend> </Chart> );};With icons and logos
Section titled “With icons and logos”Each legend item accepts an icon prop for FontAwesome icons or a logo prop (with name and src) to display a favicon.
import React from "react";import { Chart, DonutChart, Legend, LegendItem } from "stylus-ui/Chart";import { getFavicon } from "stylus-ui/utils/getFavicon";import { faGrid2 } from "@fortawesome/pro-regular-svg-icons";
const data = [ { name: "Notion", url: "https://notion.so", amount: 281 }, { name: "Slack", url: "https://slack.com", amount: 142 }, { name: "Figma", url: "https://figma.com", amount: 87 }, { name: "Others", amount: 50 },];
export default () => { const total = data.reduce((sum, item) => sum + item.amount, 0);
return ( <Chart className="flex items-center gap-8" data={data} category="name" colors={["accent-1", "accent-3", "accent-5", "accent-neutral"]} > <DonutChart value="amount" label="Yearly average" valueFormatter={(v) => `${v} hrs`} showValue /> <Legend> {data.map((item) => { const logoObj = item.url && { name: item.name, src: getFavicon({ url: item.url }), };
return ( <LegendItem key={item.name} dataKey={item.name} logo={logoObj} icon={!item.url ? faGrid2 : undefined} label={item.name} value={`${((item.amount / total) * 100).toFixed(1)}%`} /> ); })} </Legend> </Chart> );};API reference
Section titled “API reference”A donut chart component for visualizing proportional data with an optional center label. Automatically syncs with Chart context when used inside a Chart component.
value
The key in data objects to use as values (e.g., ‘amount’, ‘count’)
category
The key in data objects to use as category names (e.g., ‘name’, ‘app’). If not provided, will use category from Chart context.
colors
Accent color keys assigned positionally to slices (accent-1..accent-5, accent-neutral). Defaults to the accent series.
data
Array of data objects to display in the chart. Each object should have properties matching the category and value keys. If not provided, will use data from Chart context.
label
Optional label text to display above the center value
labelFontSize
Optional font size for the label text (in SVG viewBox units). Default: 15
showValue
Whether to hide the center value label
valueFontSize
Optional font size for the center value text (in SVG viewBox units).
valueFormatter
Function to format the center label value. Receives the total sum of all values.
Bar chart
Section titled “Bar chart”Pass index (the x-axis key) and categories (one or more series keys). Each series becomes a bar, colored positionally from the palette.
import { BarChart } from "stylus-ui/Chart";
const data = [ { team: "Store Ops", clean: 62, manual: 24, rework: 14 }, { team: "Distribution", clean: 44, manual: 34, rework: 22 }, { team: "Merchandising", clean: 58, manual: 30, rework: 12 }, { team: "Planning", clean: 70, manual: 22, rework: 8 },];
export default () => ( <div className="h-64 w-full"> <BarChart data={data} index="team" categories={["clean", "manual", "rework"]} /> </div>);Each series takes an accent color positionally (accent-1, accent-2, …). To pin or reorder, pass colors with accent keys — accent-1, accent-2, accent-3, accent-4, accent-5, or accent-neutral:
<BarChart data={data} index="team" categories={["clean", "manual", "rework"]} colors={["accent-3", "accent-1", "accent-neutral"]} />Stacked
Section titled “Stacked”Set stacked to combine every series into one bar per x position. Set layout="vertical" for horizontal bars.
import { BarChart } from "stylus-ui/Chart";
const data = [ { team: "Store Ops", clean: 62, manual: 24, rework: 14 }, { team: "Distribution", clean: 44, manual: 34, rework: 22 }, { team: "Planning", clean: 70, manual: 22, rework: 8 },];
export default () => ( <div className="h-64 w-full"> <BarChart data={data} index="team" categories={["clean", "manual", "rework"]} stacked /> </div>);Histogram
Section titled “Histogram”Use variant="histogram" to render pre-binned distribution data as gap-free bars. Compute the bins in your data adapter; the chart renders the buckets it is given.
import { BarChart } from "stylus-ui/Chart";
const bins = [ { bucket: "0–1m", count: 8 }, { bucket: "1–2m", count: 22 }, { bucket: "2–5m", count: 41 }, { bucket: "5–10m", count: 27 }, { bucket: "10m+", count: 12 },];
export default () => ( <div className="h-64 w-full"> <BarChart data={bins} index="bucket" categories={["count"]} variant="histogram" /> </div>);API reference
Section titled “API reference”A bar chart for categorical comparison, stacked composition, and pre-binned histograms. Colors and hover state sync with Chart context when nested inside a Chart component; otherwise it renders standalone from its own props.
categories
Series keys to render as bars. Each becomes one <Bar>, colored positionally.
index
The key in each data object used for the x-axis (category or histogram bin label).
categoryAxisWidth
Total width in px of the category axis on a horizontal bar chart
(layout='vertical'), the yAxisTitle strip included. No effect on vertical
bars, whose categories run along the x-axis.
Omit it: the axis fits its longest name, between 60 and 160px, and ellipsises whatever is left over. Pass a number when that default is wrong for the space you have — a wide dashboard that can spare 240px for whole names, or a narrow card that wants the plot back. An explicit width is used as given, without the clamp, and the labels are cut to fit it.
colors
Accent color keys assigned positionally to categories (accent-1..accent-5, accent-neutral). Pass an explicit list to pin a color per series. Defaults to the accent series.
data
Array of data objects, one per x-axis position. Falls back to Chart context data.
desc
Accessible description, rendered into the SVG <desc>.
emptyMessage
Content shown when there is no data. Default: “No data”.
height
Fixed chart height in px. Omit to fill the container responsively.
layout
'horizontal' renders vertical bars (x = category); 'vertical' renders horizontal bars (y = category). Default: 'horizontal'.
loading
Render a loading skeleton instead of the chart.
showGrid
Show the cartesian grid. Default: true.
showTooltip
Show the hover tooltip. Default: true.
showXAxis
Show the x-axis. Default: true.
showYAxis
Show the y-axis. Default: true.
showZeroTick
Print the value axis’ 0 label. Defaults to printing it only when no
category axis runs beneath it — the two labels collide in the origin corner
otherwise.
stacked
Stack all series into a single bar per x position.
ticks
Explicit value-axis tick positions. Omit to let recharts choose. Pass this to control the ticks when the raw domain (e.g. seconds) formats to awkward labels.
title
Accessible name for the chart, rendered into the SVG <title>. Give charts a
title so assistive tech can name the graphic.
A <title> under <svg> is the SVG spelling of a title attribute, so the
browser also shows it as a native tooltip anywhere over the plot — on top of
the hover tooltip. When the name is already visible next to the chart, point
aria-labelledby at that text instead of setting this.
valueFormatter
Formats axis + tooltip values.
variant
'histogram' renders adjacent, gap-free, square bars for pre-binned distribution data. Binning happens upstream in the data adapter, not here. Default: 'bar'.
width
Fixed chart width in px. Omit to fill the container responsively (required for tests/jsdom, which size the container to 0).
xAxisTitle
Names what the x-axis measures, under its tick labels. Omit for no title.
yAxisTitle
Names what the y-axis measures, rotated beside its tick labels. Omit for no title.
Line chart
Section titled “Line chart”Pass index (the x-axis key) and categories (the series keys). Lines interpolate as a monotone curve by default; set curveType="linear" or showDots to change the look.
import { LineChart } from "stylus-ui/Chart";
const data = [ { week: "W1", runs: 40, edits: 12 }, { week: "W2", runs: 55, edits: 18 }, { week: "W3", runs: 48, edits: 22 }, { week: "W4", runs: 63, edits: 20 }, { week: "W5", runs: 71, edits: 28 },];
export default () => ( <div className="h-64 w-full"> <LineChart data={data} index="week" categories={["runs", "edits"]} showDots /> </div>);Axis titles
Section titled “Axis titles”BarChart and LineChart both take xAxisTitle and yAxisTitle, which name what an axis measures as opposed to what its ticks read. A title names the axis it sits on, so xAxisTitle stays on the x-axis whichever way a bar chart's layout runs. Each chart reserves the room its titles need, so a chart in a narrow column keeps its plot area.
Reach for them when the unit is not obvious from the chart title — a duration axis and a count axis otherwise look the same.
import { LineChart } from "stylus-ui/Chart";
const data = [ { week: "W1", runs: 40 }, { week: "W2", runs: 55 }, { week: "W3", runs: 48 }, { week: "W4", runs: 63 }, { week: "W5", runs: 71 },];
export default () => ( <div className="h-64 w-full"> <LineChart data={data} index="week" categories={["runs"]} showDots xAxisTitle="Week" yAxisTitle="Instances" /> </div>);Axis ticks and gridlines
Section titled “Axis ticks and gridlines”Both cartesian charts pick their own value-axis ticks, and these defaults need no props:
- Every gridline carries a label. The top tick is placed at or above the largest value and the domain is pinned to it, so the data always sits below the topmost gridline.
- Counts stay whole. Ticks step on a 1/2/5 ladder, so a count axis never draws a half-unit gridline.
- A crowded category axis thins out. A long series prints about seven labels rather than one per point. Named bar categories are never thinned, because a dropped label loses a bar's name; only a histogram's bin edges are.
- The
0label steps aside where a category axis runs beneath it, since the two collide in the origin corner. The gridline stays. PassshowZeroTickto pin the behaviour either way.
Pass ticks to choose the positions yourself. For a duration axis, use niceDurationTicks rather than raw seconds: it steps in a unit the formatter prints exactly, so the labels do not read 0s, 2m, 3m, 5m.
import { LineChart, niceDurationTicks } from "stylus-ui/Chart";
const data = [ { week: "May 11", p90: 785 }, { week: "May 18", p90: 505 }, { week: "May 25", p90: 480 }, { week: "Jun 1", p90: 355 }, { week: "Jun 8", p90: 360 },];
const formatDuration = (seconds) => seconds < 60 ? `${seconds}s` : `${Math.round(seconds / 60)}m`;
export default () => ( <div className="h-64 w-full"> <LineChart data={data} index="week" categories={["p90"]} showDots valueFormatter={formatDuration} ticks={niceDurationTicks(785)} yAxisTitle="Run duration" /> </div>);With a coordinated legend
Section titled “With a coordinated legend”Wrap either chart in Chart with categories so the Legend and hover state stay in sync, exactly like DonutChart.
import { BarChart, Chart, Legend, LegendItem } from "stylus-ui/Chart";
const data = [ { team: "Store Ops", clean: 62, manual: 24, rework: 14 }, { team: "Distribution", clean: 44, manual: 34, rework: 22 }, { team: "Planning", clean: 70, manual: 22, rework: 8 },];
const categories = ["clean", "manual", "rework"];
export default () => ( <Chart categories={categories} className="flex flex-col gap-4"> <div className="h-56 w-full"> <BarChart data={data} index="team" categories={categories} stacked /> </div> <Legend className="flex-row flex-wrap gap-2"> <LegendItem dataKey="clean" label="Flows cleanly" value="" /> <LegendItem dataKey="manual" label="Manual handling" value="" /> <LegendItem dataKey="rework" label="Rework" value="" /> </Legend> </Chart>);API reference
Section titled “API reference”A line chart for trends and time series with one or more series. Set area to
fill under each line with a soft gradient. Colors and hover state sync with
Chart context when nested inside a Chart component; otherwise it renders
standalone from its own props.
categories
Series keys to render as lines. Each becomes one line, colored positionally.
index
The key in each data object used for the x-axis (e.g. a date or week label).
area
Fill the space under each line with a soft gradient of its accent color, fading to transparent. Default: false.
colors
Accent color keys assigned positionally to categories (accent-1..accent-5, accent-neutral). Pass an explicit list to pin a color per series. Defaults to the accent series.
connectNulls
Bridge gaps where a series value is null/undefined. Default: false.
curveType
Line interpolation. Default: 'monotone'.
data
Array of data objects, one per x-axis position. Falls back to Chart context data.
desc
Accessible description, rendered into the SVG <desc>.
emptyMessage
Content shown when there is no data. Default: “No data”.
height
Fixed chart height in px. Omit to fill the container responsively.
loading
Render a loading skeleton instead of the chart.
showDots
Draw a dot at each data point. Default: false.
showGrid
Show the cartesian grid. Default: true.
showTooltip
Show the hover tooltip. Default: true.
showXAxis
Show the x-axis. Default: true.
showYAxis
Show the y-axis. Default: true.
showZeroTick
Print the value axis’ 0 label. Defaults to printing it only when no x-axis
runs beneath it — the two labels collide in the origin corner otherwise.
ticks
Explicit value-axis tick positions. Omit to let recharts choose. Pass this to control the ticks when the raw domain (e.g. seconds) formats to awkward labels.
title
Accessible name for the chart, rendered into the SVG <title>. Give charts a
title so assistive tech can name the graphic.
A <title> under <svg> is the SVG spelling of a title attribute, so the
browser also shows it as a native tooltip anywhere over the plot — on top of
the hover tooltip. When the name is already visible next to the chart, point
aria-labelledby at that text instead of setting this.
valueFormatter
Formats axis + tooltip values.
width
Fixed chart width in px. Omit to fill the container responsively (required for tests/jsdom, which size the container to 0).
xAxisTitle
Names what the x-axis measures, under its tick labels. Omit for no title.
yAxisTitle
Names what the value axis measures, rotated beside its tick labels. Omit for no title.
A tokenized tooltip for cartesian charts. Replaces recharts’ hardcoded white
box with a surface that follows the brand-refresh + dark-mode token system.
Each series swatch uses the accent fill class the chart passes in
colorClassNames, so it matches the rendered bar/line exactly.
active
Whether the tooltip is active (hovering a data point). Injected by recharts.
colorClassNames
Map of series key → accent fill class, so each swatch matches its bar/line.
label
The x-axis label for the hovered point. Injected by recharts.
payload
The hovered payload entries (one per series). Injected by recharts.
valueFormatter
Formats each series value shown in the tooltip.
Shared API
Section titled “Shared API”Chart (and its useChartContext hook), Legend, and LegendItem are shared across every chart type.
Chart wrapper component that provides interaction state management and color coordination for child components. Automatically synchronizes hover, click states, and colors between DonutChart, Legend, and other chart components.
children
Child components (DonutChart, Legend, etc.)
categories
Explicit list of series keys for series charts (BarChart, LineChart). Each
key is a dataKey shared across every data point. When provided, colors are
assigned positionally to these keys and take precedence over data/category
derivation — this is the series model, as opposed to the slice model that
data + category expresses for DonutChart.
category
The key in data objects to use as category names. Required when data is provided.
colors
Accent color keys (accent-1..accent-5, accent-neutral) assigned
positionally to slices (DonutChart) or series (BarChart/LineChart). Defaults
to the accent series.
Legacy hue keys are still accepted for backward compatibility with un-migrated consumers, but they are ignored — the default accent cycle is used instead, so nothing renders a legacy color. Prefer accent keys.
data
Array of data objects. When provided with category, enables automatic color coordination between child components.
hoveredDataKey
Controlled hover state (dataKey of the hovered item). When provided, the component operates in controlled mode.
onClick
Callback fired when an item is clicked. Use this for controlled selection state.
onHover
Callback fired when an item is hovered. Use this for controlled hover state.
selectedDataKey
Controlled selection state (dataKey of the selected item). When provided, the component operates in controlled mode.
useChartContext
Section titled “useChartContext”Hook to access chart interaction context. Returns null if used outside of a Chart provider. Components can use this to sync their interaction state automatically.
Legend
Section titled “Legend”A legend component for charts that displays colored indicators with labels and values. Automatically syncs with Chart context when used inside a Chart component.
children
Child LegendItem components to display
orientation
Layout direction. 'vertical' (default) stacks the items one per row, each
with a right-aligned value column — the key beside a donut. 'horizontal'
lays them out in a wrapping row sized to their labels — the color key above a
cartesian (bar/line/area) chart, where the value column is usually omitted.
LegendItem
Section titled “LegendItem”A single item in a Legend component. Must be used as a child of Legend. Automatically syncs with Chart context when available.
dataKey
Unique dataKey for the legend item. Used for tracking active/highlighted state and syncing with chart data.
label
The label text to display (e.g., “Notion”, “Slack”, “Revenue”)
icon
Optional icon to display before the label
labelClassName
Optional className to apply to the label text
logo
Optional logo to display before the label
onClick
Callback fired when clicking the legend item. Receives the item dataKey.
size
Text size for label and value. Default: ‘sm’.
value
The value text to display (e.g., “35%”, “1,234”, “$50K”). Omit for a label-only item (e.g. a horizontal color key) to drop the value column.