Skip to content

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.

On this page

Example

Loading example…
Stacked Bar Chart.tsxtsx
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.

  1. StackedBarChart

    Native figure sharing categories and consistently ordered segments. Owns a DOM element.

  2. ChartTitle

    Native figcaption that visibly names the figure. Owns a DOM element.

  3. StackedBarChartPlot / StackedBarChartSegment

    Horizontal SVG bars divided into keyboard-reachable segments. Owns a DOM element.

  4. ChartDescription

    Visible prose summarizing totals and composition. Owns a DOM element.

  5. ChartTable

    Native table composed with segment columns and totals. Owns a DOM element.

  6. ChartTooltipOptional

    Optional floating segment label shown on hover or focus. Owns a DOM element.

Step by step

  1. 1

    Add the main part

    Start StackedBarChart with categories that share the same ordered segment labels.

  2. 2

    Add the supporting parts

    Wrap each rendered section in StackedBarChartSegment for two-dimensional arrow navigation.

  3. 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

Importtsx
import { ChartDescription, ChartTable, ChartTitle, ChartTooltip, StackedBarChart, StackedBarChartPlot, StackedBarChartSegment } from "@comp0/react";

StackedBarChart

DOM element

Native figure sharing categories and consistently ordered segments.

PropTypeDescription
valuesreadonly StackedChartValue[]Categories containing the same ordered, non-negative segments.
categoryLabelstringVisible category and numeric axis headings.
valueLabelstringVisible category and numeric axis headings.
formatValue(value: number) => stringFormats ticks, segment names, and table cells.

ChartTitle

DOM element

Native figcaption that visibly names the figure.

StackedBarChartPlot / StackedBarChartSegment

DOM element

Horizontal SVG bars divided into keyboard-reachable segments.

PropTypeDescription
aria-labelstringConcise text alternative naming the comparison and composition.
xMaxnumberOptional numeric maximum and visible tick count.
xTickCountnumberOptional numeric maximum and visible tick count.
children(segment: StackedBarChartSegmentState) => ReactNodeCustom segment renderer receiving category, segment, and geometry.
segmentStackedBarChartSegmentStateSegment state passed from the plot to StackedBarChartSegment.

ChartDescription

DOM element

Visible prose summarizing totals and composition.

ChartTable

DOM element

Native table composed with segment columns and totals.

ChartTooltip

OptionalDOM element

Optional 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 hookMeaning
[data-segment]The segment label represented by this section.
[data-active]The section currently reached by pointer or keyboard.

ChartTooltip

Style hookMeaning
[data-open]A value tooltip is visible.

Keep exploring