Sunburst Chart
A composable hierarchical sunburst chart with drill-down zoom, animated segments, breadcrumb navigation, and legend sync
Preview
Installation
pnpm dlx shadcn@latest add @bklit/sunburst-chartUsage
The Sunburst Chart uses a composable API. Map one SunburstSegment per arc in the layout, then add optional chrome for navigation and labels:
import {
buildArcs,
SunburstBreadcrumb,
SunburstCenter,
SunburstChart,
SunburstHint,
SunburstLabels,
SunburstSegment,
} from "@bklitui/ui/charts";
const data = {
name: "Revenue",
children: [
{
name: "Product",
children: [
{ name: "Enterprise", value: 198 },
{ name: "Pro", value: 145 },
],
},
{
name: "Services",
children: [
{ name: "Consulting", value: 160 },
{ name: "Support", value: 90 },
],
},
],
};
export default function RevenueSunburst() {
const { arcs } = buildArcs(data);
return (
<SunburstChart data={data} size={440}>
<SunburstBreadcrumb>
<DrillBreadcrumb />
</SunburstBreadcrumb>
{arcs.map((arc) => (
<SunburstSegment index={arc.arcIndex} key={arc.id} />
))}
<SunburstCenter />
<SunburstLabels />
<SunburstHint />
</SunburstChart>
);
}Click a segment with children to zoom in. Click the center hub or a breadcrumb crumb to zoom back out.
Breadcrumb composition
SunburstBreadcrumb is a chrome slot rendered above the SVG. Compose your own trail UI with useSunburstBreadcrumbItems():
import {
SunburstBreadcrumb,
useSunburstBreadcrumbItems,
} from "@bklitui/ui/charts";
import {
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
function DrillBreadcrumb() {
const { items, zoomTo } = useSunburstBreadcrumbItems();
return (
<BreadcrumbList>
{items.map((item, index) => (
<span className="contents" key={item.id}>
{index > 0 ? <BreadcrumbSeparator /> : null}
<BreadcrumbItem>
{item.isCurrent ? (
<BreadcrumbPage>{item.label}</BreadcrumbPage>
) : (
<BreadcrumbLink
render={
<button onClick={() => zoomTo(item.id)} type="button" />
}
>
{item.label}
</BreadcrumbLink>
)}
</BreadcrumbItem>
</span>
))}
</BreadcrumbList>
);
}Components
SunburstChart
The root component that computes the layout and provides context to children.
| Prop | Type | Default | Description |
|---|---|---|---|
data | SunburstNode | required | Hierarchical data tree |
size | number | 520 | Chart diameter in pixels |
playKey | number | 0 | Bump to replay the enter animation |
focusId | string | - | Controlled drill-down focus node id |
onFocusChange | (focusId: string) => void | - | Called when focus changes via click or breadcrumb |
hoveredIndex | number | null | - | Controlled hover — arc index in the arcs array |
onHoverChange | (index: number | null) => void | - | Hover state callback |
hoverPop | number | 8 | Segment grow distance on hover (px), cumulative along the path from root to hovered node |
padding | number | auto | Inset reserved inside the view box for hover growth (derived from depth and hoverPop) |
enterTransition | Transition | - | Override enter animation transition |
enterStaggerScale | number | 1 | Scale ring-by-ring stagger timing |
className | string | "" | Wrapper class |
SunburstSegment
Renders one arc with enter animation, drill-down click handling, and hover grow along the ancestor path.
| Prop | Type | Default | Description |
|---|---|---|---|
index | number | required | Arc index from buildArcs(data).arcs |
color | string | from data/palette | Optional color override |
fill | string | - | Optional fill for patterns/gradients (e.g. url(#patternId)) |
fillOpacity | number | depth-based | Optional fill opacity override |
SunburstCenter
Navigation hub rendered after drill-down. Click to zoom out to the parent focus.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | "" | Additional CSS class |
SunburstBreadcrumb
Chrome slot above the chart for drill-down navigation. Pass your breadcrumb UI as children. Use useSunburstBreadcrumbItems() inside children to read the trail and zoomTo.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | "mb-4" | Nav wrapper class |
children | ReactNode | required | Breadcrumb UI composed from chart context |
useSunburstBreadcrumbItems
Hook for building breadcrumb UI inside SunburstBreadcrumb:
const { items, zoomTo } = useSunburstBreadcrumbItems();
// items: { id, label, isCurrent }[]SunburstLabels
Segment name labels positioned at arc centroids. Each label fades in after its segment finishes the enter animation.
| Prop | Type | Default | Description |
|---|---|---|---|
fontSize | number | 11 | Label font size |
fill | string | var(--chart-label) | Label fill color |
stroke | string | var(--chart-background) | Outline stroke for contrast |
strokeWidth | number | 2.5 | Outline width (0 to disable) |
className | string | - | SVG group class |
SunburstHint
Contextual hint text below the chart (hover trail or zoom instructions). Rendered outside the SVG.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | "mt-3 min-h-5 text-center text-muted-foreground text-sm" | Hint wrapper class |
children | ReactNode | (ctx) => ReactNode | - | Custom hint content; receives { hintText, hoveredArc, focus } |
Custom hint example:
<SunburstHint>
{({ hintText, hoveredArc }) =>
hoveredArc ? (
<span className="font-medium">{hintText}</span>
) : (
<span>Explore the breakdown</span>
)
}
</SunburstHint>Legend
A composable legend synced to the current focus level. See the full Legend documentation for all components and options.
Data Shape
interface SunburstNode {
name: string;
value?: number; // Leaf value (parent values are summed from children)
color?: string; // Optional color override
fill?: string; // Optional fill for patterns/gradients
children?: SunburstNode[];
}Use buildArcs(data) to get the flat arcs array, maxDepth, and focusById map for controlled drill-down and legend wiring.
See the charts gallery for pattern fills, legend sync, and drill-down interaction.
Theming
The Sunburst Chart uses CSS variables for theming. Category colors default to --chart-1 through --chart-5. Inner rings use full opacity; outer rings step down in opacity for depth:
:root {
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
}Animation
- Enter reveal — Rings animate clockwise with staggered segment timing
- Label fade — Labels fade in per segment after that segment's reveal completes
- Drill-down zoom — Focus morphs matching arcs; others collapse to a point
- Hover grow — Segments on the path from root to the hovered node extend outward, capped at the thickness of one expanded ring at the first drill level; descendants shift radially to accommodate parent growth. A padded inset inside the view box (auto from
hoverPopand depth) prevents clipping. - Fade — Unrelated segments dim to 25% opacity when another branch is hovered (160ms ease-out)
All animations respect prefers-reduced-motion.
Dependencies
pnpm add motionAdd @visx/pattern when using PatternLines or other pattern fills.