Charts
Stacked Column Chart
A vertical category comparison divided into consistently ordered segments.
When to use it: Use it for a small ordered set of categories where both totals and composition change.
Example
import {
ChartDescription,
ChartTable,
ChartTitle,
ChartTooltip,
StackedColumnChart,
StackedColumnChartPlot,
StackedColumnChartSegment,
} from "@comp0/react";
const signups = [
{
label: "Q1",
segments: [
{ label: "Free", value: 44 },
{ label: "Pro", value: 18 },
],
},
{
label: "Q2",
segments: [
{ label: "Free", value: 52 },
{ label: "Pro", value: 24 },
],
},
{
label: "Q3",
segments: [
{ label: "Free", value: 49 },
{ label: "Pro", value: 31 },
],
},
{
label: "Q4",
segments: [
{ label: "Free", value: 61 },
{ label: "Pro", value: 38 },
],
},
] as const;
const colors = [
"bg-violet-600 fill-violet-600 dark:bg-violet-400 dark:fill-violet-400",
"bg-amber-500 fill-amber-500 dark:bg-amber-400 dark:fill-amber-400",
] as const;
const formatSignups = (value: number) => `${value}k`;
export function Example() {
return (
<StackedColumnChart
values={signups}
categoryLabel="Quarter"
valueLabel="Signups"
formatValue={formatSignups}
className="w-full max-w-2xl rounded-lg has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-4 has-[:focus-visible]:outline-violet-600 dark:has-[:focus-visible]:outline-violet-400"
>
<ChartTitle className="text-base font-semibold text-zinc-950 dark:text-zinc-50">
Signups by plan
</ChartTitle>
<div className="mt-3 flex gap-4 text-sm">
{signups[0].segments.map((segment, index) => (
<span key={segment.label} className="flex items-center gap-2">
<span aria-hidden="true" className={`size-3 rounded-sm ${colors[index]}`} />
{segment.label}
</span>
))}
</div>
<StackedColumnChartPlot
aria-label="Stacked column chart showing quarterly signups by plan"
className="mx-auto mt-5 aspect-square w-full max-w-md overflow-visible"
>
{(segment) => (
<StackedColumnChartSegment
key={`${segment.value.label}-${segment.segment.label}`}
segment={segment}
className="group outline-none"
>
<rect
x={segment.x}
y={segment.y}
width={segment.width}
height={segment.height}
className={`${colors[segment.segmentIndex]} stroke-transparent group-data-active:stroke-zinc-950 dark:group-data-active:stroke-white`}
strokeWidth="2"
vectorEffect="non-scaling-stroke"
/>
</StackedColumnChartSegment>
)}
</StackedColumnChartPlot>
<ChartDescription className="mt-5 text-sm text-zinc-600 dark:text-zinc-400">
Total signups peaked in the fourth quarter, with Pro accounting for a growing share.
</ChartDescription>
<ChartTable className="mt-4 w-full border-collapse text-left text-sm [&_td]:border-t [&_td]:border-zinc-200 [&_td]:py-2 [&_th]:border-zinc-200 [&_th]:py-2 dark:[&_td]:border-zinc-800 dark:[&_th]:border-zinc-800">
<caption className="sr-only">Quarterly signups by plan</caption>
<thead>
<tr>
<th scope="col">Quarter</th>
<th scope="col">Free</th>
<th scope="col">Pro</th>
<th scope="col">Total</th>
</tr>
</thead>
<tbody>
{signups.map((quarter) => (
<tr key={quarter.label}>
<th scope="row">{quarter.label}</th>
{quarter.segments.map((segment) => (
<td key={segment.label}>{formatSignups(segment.value)}</td>
))}
<td>
{formatSignups(quarter.segments.reduce((sum, segment) => sum + segment.value, 0))}
</td>
</tr>
))}
</tbody>
</ChartTable>
<ChartTooltip className="pointer-events-none z-50 rounded-md bg-zinc-950 px-2 py-1 text-sm text-white shadow-lg dark:bg-zinc-50 dark:text-zinc-950" />
</StackedColumnChart>
);
}Anatomy
Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.
A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.
StackedColumnChart
Native figure sharing categories and consistently ordered segments. Owns a DOM element.
ChartTitle
Native figcaption that visibly names the figure. Owns a DOM element.
StackedColumnChartPlot / StackedColumnChartSegment
Vertical SVG columns divided into keyboard-reachable segments. Owns a DOM element.
ChartDescription
Visible prose summarizing totals and composition. Owns a DOM element.
ChartTable
Native table composed with segment columns and totals. Owns a DOM element.
ChartTooltipOptional
Optional floating segment label shown on hover or focus. Owns a DOM element.
Step by step
- 1
Add the main part
Start StackedColumnChart with categories that share the same ordered segment labels.
- 2
Add the supporting parts
Wrap each rendered section in StackedColumnChartSegment for category and segment navigation.
- 3
Make the behavior clear
Keep segment labels visible and include a native ChartTable with segment values and totals.
Exampletsx <StackedColumnChart values={signups} categoryLabel="Quarter" valueLabel="Signups"> <ChartTitle>Signups by plan</ChartTitle> <StackedColumnChartPlot aria-label="Stacked column chart of signups"> {(segment) => ( <StackedColumnChartSegment segment={segment}> <rect x={segment.x} y={segment.y} width={segment.width} height={segment.height} /> </StackedColumnChartSegment> )} </StackedColumnChartPlot> <ChartTable> <caption>Signup values</caption> </ChartTable> <ChartTooltip /> </StackedColumnChart>;
Keyboard
- ⇥
- Enters the chart at its current segment and leaves with one more Tab.
- ←→
- Moves between categories while retaining the segment.
- ↑↓
- Moves between segments in the current category.
- HomeEnd
- Moves to the first or last segment.
- Esc
- Dismisses an open ChartTooltip.
Forms and accessibility
Charts are descriptive content and do not create form values.
Accessibility checklist
- Keep segment labels visible next to color or pattern samples.
- Wrap every section in StackedColumnChartSegment; horizontal arrows change category and vertical arrows change segment.
- Announce each section's category, segment label, and formatted value.
- Include every segment and total in a native table.
- Do not rely on hue alone to distinguish adjacent sections.
API reference
import { ChartDescription, ChartTable, ChartTitle, ChartTooltip, StackedColumnChart, StackedColumnChartPlot, StackedColumnChartSegment } from "@comp0/react";StackedColumnChart
DOM elementNative figure sharing categories and consistently ordered segments.
| Prop | Type | Description |
|---|---|---|
values | readonly StackedChartValue[] | Categories containing the same ordered, non-negative segments. |
categoryLabel | string | Visible category and numeric axis headings. |
valueLabel | string | Visible category and numeric axis headings. |
formatValue | (value: number) => string | Formats ticks, segment names, and table cells. |
ChartTitle
DOM elementNative figcaption that visibly names the figure.
StackedColumnChartPlot / StackedColumnChartSegment
DOM elementVertical SVG columns divided into keyboard-reachable segments.
| Prop | Type | Description |
|---|---|---|
aria-label | string | Concise text alternative naming the comparison and composition. |
yMax | number | Optional numeric maximum and visible tick count. |
yTickCount | number | Optional numeric maximum and visible tick count. |
children | (segment: StackedColumnChartSegmentState) => ReactNode | Custom segment renderer receiving category, segment, and geometry. |
segment | StackedColumnChartSegmentState | Segment state passed from the plot to StackedColumnChartSegment. |
ChartDescription
DOM elementVisible prose summarizing totals and composition.
ChartTable
DOM elementNative table composed with segment columns and totals.
ChartTooltip
OptionalDOM elementOptional floating segment label shown on hover or focus.
Style hooks
Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.
StackedColumnChartPlot / StackedColumnChartSegment
| Style hook | Meaning |
|---|---|
[data-segment] | The segment label represented by this section. |
[data-active] | The section currently reached by pointer or keyboard. |
ChartTooltip
| Style hook | Meaning |
|---|---|
[data-open] | A value tooltip is visible. |