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]:sr-only" />
<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 that renders a complete default from the chart data: a caption from the ChartTitle text or axis labels, column headers, and one row per value. Pass children for a custom body. 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 /> <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 that renders a complete default from the chart data: a caption from the ChartTitle text or axis labels, column headers, and one row per value. Pass children for a custom body.
ChartTooltip
OptionalDOM elementOptional floating segment label shown on hover or focus.
| Prop | Type | Description |
|---|---|---|
placement | PopoverPlacement | Side of the active mark; defaults to "top". |
offset | number | Distance from the active mark; defaults to eight pixels. |
children | ReactNode | (details: ChartValueDetails) => ReactNode | Custom content receiving the active segment's formatted details. |
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-category] | The category label of the column this segment belongs to. |
[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. |