Charts
Stacked Bar Chart
A horizontal category comparison divided into consistently ordered segments.
When to use it: Use it when long category labels and part-to-total comparisons both matter.
Example
import {
ChartDescription,
ChartTable,
ChartTitle,
ChartTooltip,
StackedBarChart,
StackedBarChartPlot,
StackedBarChartSegment,
} from "@comp0/react";
const orders = [
{
label: "Online",
segments: [
{ label: "New", value: 48 },
{ label: "Returning", value: 72 },
],
},
{
label: "Retail",
segments: [
{ label: "New", value: 34 },
{ label: "Returning", value: 51 },
],
},
{
label: "Partners",
segments: [
{ label: "New", value: 22 },
{ label: "Returning", value: 28 },
],
},
] as const;
const colors = [
"bg-teal-600 fill-teal-600 dark:bg-teal-400 dark:fill-teal-400",
"bg-sky-600 fill-sky-600 dark:bg-sky-400 dark:fill-sky-400",
] as const;
const formatOrders = (value: number) => `${value}k`;
export function Example() {
return (
<StackedBarChart
values={orders}
categoryLabel="Channel"
valueLabel="Orders"
formatValue={formatOrders}
className="w-full max-w-2xl rounded-lg has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-4 has-[:focus-visible]:outline-teal-600 dark:has-[:focus-visible]:outline-teal-400"
>
<ChartTitle className="text-base font-semibold text-zinc-950 dark:text-zinc-50">
Orders by channel and customer type
</ChartTitle>
<div className="mt-3 flex gap-4 text-sm">
{orders[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>
<StackedBarChartPlot
aria-label="Stacked horizontal bar chart comparing orders by channel and customer type"
className="mx-auto mt-5 aspect-square w-full max-w-md overflow-visible"
>
{(segment) => (
<StackedBarChartSegment
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"
/>
</StackedBarChartSegment>
)}
</StackedBarChartPlot>
<ChartDescription className="mt-5 text-sm text-zinc-600 dark:text-zinc-400">
Online orders lead both customer groups, and returning customers are the larger segment in
every channel.
</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">Orders by channel and customer type</caption>
<thead>
<tr>
<th scope="col">Channel</th>
<th scope="col">New</th>
<th scope="col">Returning</th>
<th scope="col">Total</th>
</tr>
</thead>
<tbody>
{orders.map((channel) => (
<tr key={channel.label}>
<th scope="row">{channel.label}</th>
{channel.segments.map((segment) => (
<td key={segment.label}>{formatOrders(segment.value)}</td>
))}
<td>
{formatOrders(channel.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" />
</StackedBarChart>
);
}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.
StackedBarChart
Native figure sharing categories and consistently ordered segments. Owns a DOM element.
ChartTitle
Native figcaption that visibly names the figure. Owns a DOM element.
StackedBarChartPlot / StackedBarChartSegment
Horizontal SVG bars 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 StackedBarChart with categories that share the same ordered segment labels.
- 2
Add the supporting parts
Wrap each rendered section in StackedBarChartSegment for two-dimensional arrow navigation.
- 3
Make the behavior clear
Keep segment labels visible and include a native ChartTable with segment values and totals.
Exampletsx <StackedBarChart values={orders} categoryLabel="Channel" valueLabel="Orders"> <ChartTitle>Orders by channel</ChartTitle> <StackedBarChartPlot aria-label="Stacked bar chart of orders"> {(segment) => ( <StackedBarChartSegment segment={segment}> <rect x={segment.x} y={segment.y} width={segment.width} height={segment.height} /> </StackedBarChartSegment> )} </StackedBarChartPlot> <ChartTable> <caption>Order values</caption> </ChartTable> <ChartTooltip /> </StackedBarChart>;
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 StackedBarChartSegment; vertical arrows change category and horizontal 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, StackedBarChart, StackedBarChartPlot, StackedBarChartSegment } from "@comp0/react";StackedBarChart
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.
StackedBarChartPlot / StackedBarChartSegment
DOM elementHorizontal SVG bars divided into keyboard-reachable segments.
| Prop | Type | Description |
|---|---|---|
aria-label | string | Concise text alternative naming the comparison and composition. |
xMax | number | Optional numeric maximum and visible tick count. |
xTickCount | number | Optional numeric maximum and visible tick count. |
children | (segment: StackedBarChartSegmentState) => ReactNode | Custom segment renderer receiving category, segment, and geometry. |
segment | StackedBarChartSegmentState | Segment state passed from the plot to StackedBarChartSegment. |
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.
StackedBarChartPlot / StackedBarChartSegment
| 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. |