DocumentationComponentsTheme Creator
Theme CreatorGitHubIntroduction
InstallationUsageThemingMotion
Components
ChartArea ChartBar ChartLine ChartLive Line ChartComposed ChartPie ChartFunnel ChartRadar ChartRadial ChartSankey ChartScatter ChartCandlestick ChartSunburst ChartChoropleth ChartActivity Heatmap
AI agentsContributing
  1. Components
  2. Sunburst Chart

Sunburst Chart

Token-driven sunburst chart built on Recharts v3 — a part-to-whole hierarchy as concentric rings, with click and keyboard drill-down, a centre back control, a breadcrumb and a legend that follows the pointer.

$ pnpm add @tessinaui/ui

When to use

  • For a whole whose parts have parts. Revenue by product, then plan, then region; a budget by department, then team. Each ring splits the ring inside it.
  • To find where most of the whole sits, level by level. The largest arc on each ring is the first thing the eye lands on.
  • When readers need to explore. A segment opens out to the whole circle, so a deep tree can be read one branch at a time (Drill-down).

When not to use

  • For a flat list of parts. One level is a PieChart.
  • To compare values precisely. Arc length on a ring is hard to rank. Use a BarChart, or put the numbers in a Table.
  • For values that do not add up to a whole, or that go negative. A share cannot be negative; the chart warns in development and draws nothing for it.
  • For flows between stages. Money moving from one bucket to another is a flow, not a hierarchy.
  • For more than about eight parts on a ring. Arcs become slivers that cannot be read, pointed at or labelled. Fold the tail into "Other" (Group the tail).

Installation

Terminal
pnpm add @tessinaui/ui recharts

Usage

example.tsx
import { SunburstChart, type SunburstNode } from "@tessinaui/ui";

// One root; a leaf carries a value, a parent is the sum of its children.
const data: SunburstNode = {
  name: "Revenue",
  children: [
    {
      id: "cloud",
      name: "Cloud",
      children: [
        { id: "pro", name: "Pro", value: 496 },
        { id: "team", name: "Team", value: 227 },
      ],
    },
    { id: "desktop", name: "Desktop", value: 199 },
  ],
};

<SunburstChart
  data={data}
  label="Cloud brings in almost four fifths of revenue"
  description="Revenue of 922: Cloud 723, Desktop 199."
/>

Examples

Default

The first ring is the root's children. Each ring outward is one step lighter, and every other branch starts one step darker, so two neighbours never share a tone where they meet. Point at a segment and everything off its path steps back.

Drill-down

Click a segment, or focus it and press Enter, and it opens out to the whole circle. The hub then reads "back": it, a crumb in the trail or Escape goes up a level. Under reduced motion the chart changes view without the zoom.

Controlled path

path holds the drill state: the ids from the root's first child down. Keep it in the URL or move it from outside the chart; onPathChange reports every drill. onNodeClick reports a click on any segment, a leaf included.

Rings

levels sets how many rings are drawn below the current view. Three is the default and the most the tones can carry in the light theme; deeper parts appear as the reader drills.

Branch colours

A colour in config, keyed by a node's id, tints that node's whole branch, and each ring outward lifts it one step. Measure every colour you pass against the page in both themes.

Labels

showLabels prints names along the radius, kept upright. A name is drawn only where it fits its ring; the rest stay in the tooltip, the hub and the legend.

Tooltip and formatting

valueFormatter spells every number: the hub, the legend, the tooltip and each segment's accessible name. The tooltip adds the path, the share of the parent and the share of the total.

Legend

The legend lists the ring around the hub with each value and share. Point at a row and its branch stands out; point at a segment and its row does. A row with parts inside it is a button that opens it, the same as the segment.

Group the tail

Fold small children into one "Other" node before the data reaches the chart. One "Other" can be read and pressed; ten slivers cannot.

In a card

Corner rounding

rounded rounds every segment's corners, resolved per segment: a thin segment takes the largest corner it can carry.

Sizes

size caps the diameter at 240, 320 or 400 pixels. Below the cap the chart fills its container.

Accessible name and data table

On a coloured plate

Inside a coloured Surface every segment is the plate's ink: full on the first ring, 80% on the next, alternating outward. The seams and the labels tell neighbours apart, so turn labels on.

Right-to-left

The rings run clockwise from twelve o'clock in every direction: a circle has no reading direction. The trail, the legend, the tooltip and the left and right arrow keys follow dir.

Narrow container

States

API Reference

PropTypeDefaultDescription
dataSunburstNode—The root: { id?, name, value?, children? }. A leaf carries value; a parent is the sum of its children.
configChartConfig{}Colours and labels keyed by node id. A colour tints the node's whole branch.
levelsnumber3Rings drawn below the current view.
pathstring[]—The drill path, controlled: node ids from the root's first child down.
defaultPathstring[][]The drill path to start on, uncontrolled.
onPathChange(path: string[]) => void—Called on every drill in or out.
onNodeClick(node: SunburstChartNodeInfo) => void—Called when a segment or legend row is clicked, before any drill.
valueFormatter(value: number, node) => stringIntl.NumberFormatFormats every number the chart prints.
showLabelsbooleanfalseNames on the arcs, where they fit.
showBreadcrumbbooleantrueThe trail above the chart.
showLegendbooleantrueThe list of the first ring below the chart.
showTooltipbooleantrueThe tooltip on pointer hover.
size"sm" | "md" | "lg""md"Caps the diameter at 240, 320 or 400 pixels.
rounded"none" | "sm" | "md" | "lg" | "full""none"Corner rounding of each segment.
localestringfollows dirLocale for numbers and shares.
dir"ltr" | "rtl"inheritedReading direction, and the language of the built-in strings.
stringsPartial<SunburstChartStrings>—Overrides for the built-in strings: the trail's name, segment names, the hub, the announcement.
labelstring—Accessible name of the figure and the tree. State the insight.
descriptionstring—Visually hidden summary for assistive tech.
loadingbooleanfalseRender the skeleton.
errorReactNode—Render an error message instead of the chart.
emptyStateReactNode"No data to display"Shown when nothing in the tree has a value.

A node's id is its own id (or its name), and its full id is the path of ids below the root joined by /: "cloud/pro/eu". Give each node an id unique among its siblings, so two regions called "EU" under different plans never collide. path and onPathChange speak full ids.

Colour

Segments are tones of --chart-1. A ring is one step (l + 0.08) lighter than the ring inside it, and every other branch of the first ring starts one step darker. In the light theme three steps from the base stay at or above 3:1 against the page (5.92, 4.53 and 3.43:1); a fourth reads 2.61:1, which is why levels defaults to three. In the dark theme a lighter step only gains contrast (from 5.05:1 up).

Two neighbouring steps differ from each other by only about 1.3:1, so colour never separates two segments on its own: a seam in the colour behind the chart runs between every segment and every ring, and names carry the meaning in the tooltip, the hub, the legend and on the arcs.

Label ink follows the tone: the page's background ink over the darker steps, the text ink over the lighter ones in the light theme. A colour you pass in config keeps the same rule, so check its labels.

Accessibility

The chart is a figure named by label, and its rings are a tree: each segment is a treeitem named "Pro, $496K, 61% of Cloud", with its level, its position among its siblings and, for a parent, whether its children are drawn.

KeyAction
TabFocus the chart (one stop — the last segment focused, else the first)
← / →Previous / next segment around the ring, in reading order; wraps
↓First part of the focused segment, on the next ring out
↑The segment it sits in, on the next ring in
Home / EndFirst / last segment among its siblings
Enter / SpaceOpen the focused segment
EscGo back up a level (does nothing at the top, so an enclosing dialog still gets it)

After a drill, focus moves to the first part of the segment you opened, or back to the branch you came out of, and a polite announcement says what is showing ("Showing Cloud, 3 parts"). The hub and the trail are real buttons with names; the focused segment carries a ring drawn along its own outline.

A thin segment is too small to press with a finger. Its row in the legend, and the keyboard, reach the same part with a full-size target. The example's data table gives every value as text.

Boundaries

  • No parent value of its own. A parent is always the sum of its children; a different value on a parent is ignored, with a development warning.
  • No negative or zero parts. They have no arc, so they are not drawn.
  • No outside labels with leader lines. Names sit on the arcs or in the legend.
  • No multi-select. A click opens a segment; onNodeClick reports it.

Theming

Segment tones come from --chart-1, or the colours you pass in config; both themes are handled by the tokens. The seams are the colour behind the chart, the hub and legend use the page's text tokens, and everything follows a coloured Surface without a prop.

Candlestick ChartChoropleth Chart

On this page

When to useWhen not to useInstallationUsageExamplesDefaultDrill-downControlled pathRingsBranch coloursLabelsTooltip and formattingLegendGroup the tailIn a cardCorner roundingSizesAccessible name and data tableOn a coloured plateRight-to-leftNarrow containerStatesAPI ReferenceColourAccessibilityBoundariesTheming