;
export function ProductionMotionChart({ entrance = "independent" }: { entrance?: Entrance }) {
return (
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/combo-motion/README.md)
- [LICENSE](/charts/docs/examples/combo-motion/LICENSE)
Line/Area reveal across the plot; Bar reveals from numeric zero clamped to its domain. Interaction or data/layout/visibility changes finish entrance; reduced motion disables Kind motion.
## API reference
Focused props for explicit composition; upstream references cover additional native geometry.
### ComboChart
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `Row[]` | — | Shared category observations; Kind does not aggregate or transform them. |
| `animate` | `boolean \| ComboAnimation` | `false` | Chart-owned entrance and inspection motion, respecting reduced motion. |
| `layout` | `"horizontal" \| "vertical"` | `"horizontal"` | Native chart orientation; supply matching category and numeric axes. |
| `stackOffset` | `"none" \| "expand" \| "wiggle" \| "silhouette" \| "sign" \| "positive"` | `"none"` | Native stack calculation; formatting remains consumer-owned. |
| `barCategoryGap` / `barGap` | `number \| string` | `"10%"` / `4` | Category and grouped-bar spacing in native geometry. |
| `accessibilityLayer` | `boolean` | `true` in Recharts | Keyboard category inspection; enabled explicitly in these components. |
| `aria-label` / `aria-labelledby` | `string` | — | Consumer-supplied accessible plot name. |
| `children` | `ReactNode` | — | Explicit axes, grid, series and tooltip. |
See [Recharts ComposedChart](https://recharts.github.io/en-US/api/ComposedChart/).
### ComboAnimation
Pass to `ComboChart.animate`. Family options affect entrances only; hover transitions remain shared. Live reduced-motion changes also settle motion.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `revealDurationMs` | `number` | `1000` | Shared entrance duration in milliseconds. |
| `revealEasing` | `Transition["ease"]` | `[0.25, 0.1, 0.25, 1]` | Motion entrance easing. |
| `lineReveal` / `areaReveal` / `barReveal` | `false \| { revealDurationMs?: number; revealEasing?: Transition["ease"] }` | Shared options | Override one family's duration or easing; `false` skips its entrance. |
| `hoverTransition` | `Transition` | Spring: `210` stiffness, `28` damping, `0.8` mass | Shared inspection motion; family entrance options do not override it. |
### BarSeries
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `dataKey` | `string \| number \| ((row: Row) => unknown)` | Required | Native value field or accessor. |
| `seriesKey` | `string` | String `dataKey` | Config and visibility identity; supply for controlled accessor or numeric keys. |
| `yAxisId` / `xAxisId` | `string \| number` | `0` | Match the explicit native axes. |
| `stackId` | `string \| number` | — | Stack bars on matching axes; omit for grouped bars. |
| `maxBarSize` | `number` | — | Bound native bar thickness. |
| `radius` | `number \| [number, number, number, number]` | `0` | Native corners; stack cap geometry remains consumer-owned. |
| `fill` | `string` | Config color | Explicit paint override. |
| `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Static bar finish; custom shape, filter or active renderer retains ownership. |
Kind disables native per-series animation so `ComboChart` owns entrances. See [Recharts Bar](https://recharts.github.io/en-US/api/Bar/).
### LineSeries
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `dataKey` | `string \| number \| ((row: Row) => unknown)` | Required | Native line values. |
| `seriesKey` | `string` | String `dataKey` | Config and visibility identity. |
| `yAxisId` / `xAxisId` | `string \| number` | `0` | Bind the line to its scale; margin uses the percentage axis. |
| `type` | `"basis" \| "basisClosed" \| "basisOpen" \| "bumpX" \| "bumpY" \| "bump" \| "linear" \| "linearClosed" \| "natural" \| "monotoneX" \| "monotoneY" \| "monotone" \| "step" \| "stepBefore" \| "stepAfter"` or custom curve function | `"linear"` | Native interpolation. |
| `stroke` / `strokeDasharray` | `string` | Config color / — | Override line paint and distinguish targets with dashes. |
| `dot` | `boolean` or native dot props/renderer | `true` | Ordinary point marks; these components disable them. |
| `connectNulls` | `boolean` | `false` | Connect gaps explicitly. |
Line material choices apply to the line independently of Area and Bar. See [Recharts Line](https://recharts.github.io/en-US/api/Line/) and [Line components](/charts/docs/components/line/).
### AreaSeries
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `dataKey` | `string \| number \| ((row: Row) => unknown)` | Required | Native area values. |
| `seriesKey` | `string` | String `dataKey` | Config and visibility identity. |
| `yAxisId` / `xAxisId` | `string \| number` | `0` | Bind the area to matching axes. |
| `type` | `"basis" \| "basisClosed" \| "basisOpen" \| "bumpX" \| "bumpY" \| "bump" \| "linear" \| "linearClosed" \| "natural" \| "monotoneX" \| "monotoneY" \| "monotone" \| "step" \| "stepBefore" \| "stepAfter"` or custom curve function | `"linear"` | Choose interpolation per area. |
| `stackId` | `string \| number` | — | Stack areas on matching axes. |
| `fill` / `stroke` | `string` | Config color | Override fill and outline independently. |
| `fillOpacity` | `number` | `0.6` in Recharts | Keep the filled context legible behind other marks. |
| `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Static area finish; explicit shape or filter wins. |
See [Recharts Area](https://recharts.github.io/en-US/api/Area/) and [Area components](/charts/docs/components/area/).
## Shared components
[Root](/charts/docs/chart-components/root/), [SeriesConfig](/charts/docs/chart-components/series-config/), [Axes and grid](/charts/docs/chart-components/axes-grid/), [Legend](/charts/docs/chart-components/legend/), [Tooltip](/charts/docs/chart-components/tooltip/), [ResponsiveContainer](/charts/docs/chart-components/responsive-container/).
---
# Heatmap
Compare numeric intensity across two ordered domains with native table cells.
## Complete Heatmap consumer
Support demand across four days and six two-hour intervals.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { SupportHeatmap } from "./examples/heatmap/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/heatmap/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const rows = ["Mon", "Tue", "Wed", "Thu"];
const columns = ["08:00", "10:00", "12:00", "14:00", "16:00", "18:00"];
const data: readonly Chart.HeatmapDatum[] = [
{ row: "Mon", column: "08:00", value: 12 },
{ row: "Mon", column: "10:00", value: 34 },
{ row: "Mon", column: "12:00", value: 48 },
{ row: "Mon", column: "14:00", value: 39 },
{ row: "Mon", column: "16:00", value: 27 },
{ row: "Mon", column: "18:00", value: 8 },
{ row: "Tue", column: "08:00", value: 16 },
{ row: "Tue", column: "10:00", value: 42 },
{ row: "Tue", column: "12:00", value: 56 },
{ row: "Tue", column: "14:00", value: 45 },
{ row: "Tue", column: "16:00", value: 31 },
{ row: "Tue", column: "18:00", value: 0 },
{ row: "Wed", column: "08:00", value: 11 },
{ row: "Wed", column: "10:00", value: 29 },
{ row: "Wed", column: "12:00", value: null },
{ row: "Wed", column: "14:00", value: 41 },
{ row: "Wed", column: "16:00", value: 24 },
{ row: "Wed", column: "18:00", value: 6 },
{ row: "Thu", column: "08:00", value: 18 },
{ row: "Thu", column: "10:00", value: 47 },
{ row: "Thu", column: "12:00", value: 60 },
{ row: "Thu", column: "14:00", value: 52 },
{ row: "Thu", column: "16:00", value: 33 },
];
const scale = Chart.createHeatmapScale({
domain: [0, 60],
colors: ["#eff6ff", "#93c5fd", "#2563eb", "#1e3a8a"],
});
export function SupportHeatmap() {
return (
({ style: { height: "2rem" } })}
/>
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/heatmap/README.md)
- [LICENSE](/charts/docs/examples/heatmap/LICENSE)
## Usage
`HeatmapChart` owns the matrix model and inspection. Supply ordered domains, numeric records and one scale; compose the grid, legend and tooltip as children.
```tsx title="Composition"
```
Domain order determines equal-cell layout. Missing records and `null` use patterned cells; zero remains measured. Compose one grid and at most one tooltip per chart.
## Compact activity grid
`layout` gives contributions fixed square cells and visual header controls. The grid keeps row/column headers and explicit cell associations when labels are hidden.
## Complete Compact activity grid consumer
Seven days and twenty-six weeks use fixed square cells and visually hidden headers.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { CompactActivityHeatmap } from "./examples/heatmap-compact/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/heatmap-compact/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const rows = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
const columns = Array.from({ length: 26 }, (_, index) => `Week ${index + 1}`);
const data = rows.flatMap((row, r) =>
columns.map((column, c) => ({ row, column, value: (r + c) % 5 })),
);
const scale = Chart.createHeatmapScale({ domain: [0, 4], colors: ["#eef4eb", "#327448"] });
export function CompactActivityHeatmap() {
return (
}
/>
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/heatmap-compact/README.md)
- [LICENSE](/charts/docs/examples/heatmap-compact/LICENSE)
Numbers use pixels; valid CSS lengths such as `"1rem"` work too. Omit `cellSize` for fluid sizing. A width-constrained host retains horizontal scrolling; custom content or native cell styles can enlarge cells. Use `min-width: 0` when a flex/grid host must shrink.
## Signed comparisons
A symmetric `[-20, 20]` scale centers zero between below-target orange and above-target blue. Keep the same domain for comparisons; out-of-range values clamp to endpoint colors.
## Complete Signed comparisons consumer
Regional orders compared with their monthly target on a symmetric scale.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { RegionalHeatmap } from "./examples/heatmap-diverging/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/heatmap-diverging/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const rows = ["North", "South", "East", "West"];
const columns = ["Apr", "May", "Jun", "Jul"];
const data: readonly Chart.HeatmapDatum[] = [
{ row: "North", column: "Apr", value: -12 },
{ row: "North", column: "May", value: -6 },
{ row: "North", column: "Jun", value: 0 },
{ row: "North", column: "Jul", value: 14 },
{ row: "South", column: "Apr", value: 4 },
{ row: "South", column: "May", value: 11 },
{ row: "South", column: "Jun", value: 18 },
{ row: "South", column: "Jul", value: 7 },
{ row: "East", column: "Apr", value: -20 },
{ row: "East", column: "May", value: -9 },
{ row: "East", column: "Jun", value: -3 },
{ row: "East", column: "Jul", value: null },
{ row: "West", column: "Apr", value: 2 },
{ row: "West", column: "May", value: 0 },
{ row: "West", column: "Jun", value: 9 },
{ row: "West", column: "Jul", value: 20 },
];
const scale = Chart.createHeatmapScale({
domain: [-20, 20],
colors: ["#9a3412", "#fff7ed", "#1e40af"],
});
const formatValue = (value: number) => `${value > 0 ? "+" : ""}${value}%`;
export function RegionalHeatmap() {
return (
({ style: { height: "2rem" } })}
/>
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/heatmap-diverging/README.md)
- [LICENSE](/charts/docs/examples/heatmap-diverging/LICENSE)
Use sequential palettes for nonnegative counts. Calendar layouts require consumer-owned day/week domains, date parsing and labels.
## Cell materials
Choose plain, paper, clay or glow on cell edges. The central 84% retains the exact scale color; missing cells retain their pattern. Consumer paint overrides can change encoding.
## Complete Cell materials consumer
Plain, paper, clay and glow edge treatments preserve quantitative cell centers.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { MaterialHeatmap } from "./examples/heatmap-materials/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/heatmap-materials/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const rows = ["Mon", "Tue", "Wed", "Thu"];
const columns = ["08:00", "10:00", "12:00", "14:00", "16:00", "18:00"];
const data: readonly Chart.HeatmapDatum[] = [
{ row: "Mon", column: "08:00", value: 12 },
{ row: "Mon", column: "10:00", value: 34 },
{ row: "Mon", column: "12:00", value: 48 },
{ row: "Mon", column: "14:00", value: 39 },
{ row: "Mon", column: "16:00", value: 27 },
{ row: "Mon", column: "18:00", value: 8 },
{ row: "Tue", column: "08:00", value: 16 },
{ row: "Tue", column: "10:00", value: 42 },
{ row: "Tue", column: "12:00", value: 56 },
{ row: "Tue", column: "14:00", value: 45 },
{ row: "Tue", column: "16:00", value: 31 },
{ row: "Tue", column: "18:00", value: 0 },
{ row: "Wed", column: "08:00", value: 11 },
{ row: "Wed", column: "10:00", value: 29 },
{ row: "Wed", column: "12:00", value: null },
{ row: "Wed", column: "14:00", value: 41 },
{ row: "Wed", column: "16:00", value: 24 },
{ row: "Wed", column: "18:00", value: 6 },
{ row: "Thu", column: "08:00", value: 18 },
{ row: "Thu", column: "10:00", value: 47 },
{ row: "Thu", column: "12:00", value: 60 },
{ row: "Thu", column: "14:00", value: 52 },
{ row: "Thu", column: "16:00", value: 33 },
];
const scale = Chart.createHeatmapScale({
domain: [0, 60],
colors: ["#eff6ff", "#93c5fd", "#2563eb", "#1e3a8a"],
});
export function MaterialHeatmap({ material = "paper" }: { material?: Chart.HeatmapMaterial }) {
return (
({ style: { height: "2rem" } })}
material={material}
/>
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/heatmap-materials/README.md)
- [LICENSE](/charts/docs/examples/heatmap-materials/LICENSE)
## Keyboard and motion
Tab enters/leaves the grid; arrows move between cells. Home/End reach row ends; Ctrl+Home/End reach matrix corners. Escape dismisses inspection. See the [data grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).
Entrance fades cells diagonally, preserving geometry and colors. Interaction, input changes or resize finish it; reduced motion skips it.
## API reference
Native HTML components with an independent Kind context; no `Root` or Recharts composition is needed.
### HeatmapChart
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `rows` / `columns` | `readonly string[]` | Required | Unique, explicitly ordered domains. Records outside them throw. |
| `data` | `readonly HeatmapDatum[]` | Required | Records shaped as `{ row: string; column: string; value: number \| null }`. Nonfinite values throw. |
| `scale` | `HeatmapScale` | Required | Shared numeric encoding for grid, legend and tooltip content. |
| `duplicates` | `"error" \| "first" \| "last" \| "sum"` | `"error"` | Coordinate collision policy. Sum ignores null records; all-null coordinates stay missing. Nonfinite sums throw. |
| `formatValue` | `(value: number) => string` | `String` conversion | Formats cells, tooltip, legend and data table without changing the numeric scale. |
| `missingLabel` | `string` | `"Missing"` | Text used for absent or null observations. |
| `animate` | `boolean` | `false` | Diagonal cell entrance; respects reduced motion and interaction. |
### createHeatmapScale
Produces the `HeatmapScale` passed to `HeatmapChart`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `domain` | `readonly [number, number]` | Required | Finite ascending endpoints; their difference must also be finite. Equal endpoints use the palette midpoint. |
| `colors` | `readonly string[]` | Required | At least two opaque `#rrggbb` stops, evenly spaced in sRGB. |
`color(value)` interpolates opaque hex colors and clamps to the explicit domain. Nonfinite values throw; data does not infer the scale or categorical palette.
### HeatmapGrid
Captioned native table with row/column headers and roving grid focus. Empty domains show an empty state; wide matrices scroll horizontally.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `caption` | `string` | Required | Visible table caption and accessible grid name. |
| `layout` | `{ cellSize?: number \| string; gap?: number \| string; rowLabels?: "visible" \| "hidden"; columnLabels?: "visible" \| "hidden" }` | Fluid cells; `gap: 3`; labels visible | Optional fixed cells, spacing and independent visual headers. |
| `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Static cell-edge finish; missing cells do not receive it. |
| `Cell` | `ComponentType` | `HeatmapCellContent` | Custom cell content receives `cell`, `fill` and `formattedValue`. Keep content compatible with grid navigation. |
| `cellProps` | `(cell: HeatmapCell) => ComponentPropsWithRef<"td">` | — | Native styles, refs and handlers. The grid still owns cell semantics, focus and background color. |
| `rowLabel` / `columnLabel` | `(key: string) => ReactNode` | Domain key | Customize visible headers; coordinate identities and inspection labels remain domain keys. |
`cellSize` must be positive; `gap` must be nonnegative, including zero. Numeric values must be finite. Native table `style`, custom `Cell` and `cellProps` keep their existing overrides. Hidden labels retain grid names, header IDs and keyboard navigation; `HeatmapDataTable` is independent.
Each `HeatmapCell` retains row/column identities, indices, value and every source record. See [native table semantics](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/table).
### HeatmapCellContent
Formats values and chooses black/white text from hex-fill luminance. Also usable inside a custom `Cell`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `cell` | `HeatmapCell` | Required | Normalized coordinate supplied to a custom cell component. |
| `fill` | `string` | Required | Opaque scale hex color or the missing-fill CSS variable. |
| `formattedValue` | `string` | Required | Display text; the default renderer does not format the number again. |
### HeatmapLegend
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | Required | Fieldset legend describing the numeric encoding. |
Shows scale endpoints, zero for signed domains and the `missingLabel` swatch. It is a numeric key, without series toggles.
### HeatmapTooltip
In-flow inspection below the grid, linked to the active cell by `aria-describedby`. Focus, hover and touch inspect the same value.
Shuffle affects numeric digits only; assistive text exposes the final value immediately. Reduced motion uses plain text.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `Content` | `ComponentType` | Coordinate and value text | Custom content receives the active `cell`, `fill` and `formattedValue`; presentation stays consumer-owned. |
| `valueAnimation` | `"shuffle"` | Off | Optional digit motion for built-in numeric content. Missing cells remain text; custom `Content` receives no injected animation. |
### HeatmapDataTable
Static table using the shared model and formatter. The consumer chooses visible or visually hidden presentation; these examples preserve every observation.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `caption` | `string` | Required | Native data table caption. |
### createHeatmapModel
Normalizes ordered domains into a two-dimensional cell array, filling missing coordinates with null and retaining source records. Accepts the chart’s rows, columns, data and duplicate policy; invalid input throws.
---
# Histogram
Show response-time distributions with explicit numeric bins.
## Complete Histogram consumer
Counts across explicit equal-width response-time intervals.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { CheckoutLatencyHistogram } from "./examples/histogram/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/histogram/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Request 1", milliseconds: 12 },
{ period: "Request 2", milliseconds: 18 },
{ period: "Request 3", milliseconds: 23 },
{ period: "Request 4", milliseconds: 25 },
{ period: "Request 5", milliseconds: 27 },
{ period: "Request 6", milliseconds: 30 },
{ period: "Request 7", milliseconds: 31 },
{ period: "Request 8", milliseconds: 32 },
{ period: "Request 9", milliseconds: 34 },
{ period: "Request 10", milliseconds: 35 },
{ period: "Request 11", milliseconds: 36 },
{ period: "Request 12", milliseconds: 38 },
{ period: "Request 13", milliseconds: 40 },
{ period: "Request 14", milliseconds: 42 },
{ period: "Request 15", milliseconds: 43 },
{ period: "Request 16", milliseconds: 45 },
{ period: "Request 17", milliseconds: 47 },
{ period: "Request 18", milliseconds: 49 },
{ period: "Request 19", milliseconds: 50 },
{ period: "Request 20", milliseconds: 52 },
{ period: "Request 21", milliseconds: 55 },
{ period: "Request 22", milliseconds: 58 },
{ period: "Request 23", milliseconds: 60 },
{ period: "Request 24", milliseconds: 63 },
{ period: "Request 25", milliseconds: 66 },
{ period: "Request 26", milliseconds: 70 },
{ period: "Request 27", milliseconds: 74 },
{ period: "Request 28", milliseconds: 80 },
{ period: "Request 29", milliseconds: 88 },
{ period: "Request 30", milliseconds: 95 },
{ period: "Request 31", milliseconds: 100 },
{ period: "Request 32", milliseconds: 110 },
{ period: "Request 33", milliseconds: 125 },
{ period: "Request 34", milliseconds: 150 },
{ period: "Request 35", milliseconds: 180 },
{ period: "Request 36", milliseconds: 200 },
{ period: "Missing reading", milliseconds: null },
{ period: "Outside window", milliseconds: 260 },
];
const result = Chart.binHistogram(
data.map((row) => row.milliseconds),
[0, 40, 80, 120, 160, 200],
);
const config = {
count: { label: "Requests", color: "#733bff" },
} satisfies Chart.SeriesConfig;
export function CheckoutLatencyHistogram() {
return (
`${value} ms`,
}}
yAxisProps={{
axisLine: false,
tickLine: false,
tickMargin: 12,
width: 40,
allowDecimals: false,
}}
>
{
const bin = payload[0]?.payload as Chart.HistogramBin | undefined;
return bin
? `${bin.lower}–${bin.upper} ms${bin.upper === 200 ? " (final edge included)" : " (upper edge excluded)"}`
: "Response time";
}}
/>
{result.accepted} accepted · {result.missing} missing · {result.nonfinite} nonfinite ·{" "}
{result.outOfRange} outside 0–200 ms
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/histogram/README.md)
- [LICENSE](/charts/docs/examples/histogram/LICENSE)
## Usage
Use `binHistogram` to prepare numeric bins for `HistogramSeries`.
```tsx title="Composition"
```
Bins include their lower edge; only the last bin includes its upper edge. Interior-edge samples go right. Accepted, missing, nonfinite and out-of-range samples are counted separately.
Count height shows observations; rectangle width is the numeric interval span.
## Unequal-width density
Density is `count / (accepted × bin width)`. Nonempty rectangle areas sum to one; heights need not. Empty totals yield zero density, and discarded samples do not enter normalization.
## Complete Unequal-width density consumer
Probability density across explicit unequal-width response-time intervals.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { CheckoutDensityHistogram } from "./examples/histogram-density/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/histogram-density/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Request 1", milliseconds: 12 },
{ period: "Request 2", milliseconds: 18 },
{ period: "Request 3", milliseconds: 23 },
{ period: "Request 4", milliseconds: 25 },
{ period: "Request 5", milliseconds: 27 },
{ period: "Request 6", milliseconds: 30 },
{ period: "Request 7", milliseconds: 31 },
{ period: "Request 8", milliseconds: 32 },
{ period: "Request 9", milliseconds: 34 },
{ period: "Request 10", milliseconds: 35 },
{ period: "Request 11", milliseconds: 36 },
{ period: "Request 12", milliseconds: 38 },
{ period: "Request 13", milliseconds: 40 },
{ period: "Request 14", milliseconds: 42 },
{ period: "Request 15", milliseconds: 43 },
{ period: "Request 16", milliseconds: 45 },
{ period: "Request 17", milliseconds: 47 },
{ period: "Request 18", milliseconds: 49 },
{ period: "Request 19", milliseconds: 50 },
{ period: "Request 20", milliseconds: 52 },
{ period: "Request 21", milliseconds: 55 },
{ period: "Request 22", milliseconds: 58 },
{ period: "Request 23", milliseconds: 60 },
{ period: "Request 24", milliseconds: 63 },
{ period: "Request 25", milliseconds: 66 },
{ period: "Request 26", milliseconds: 70 },
{ period: "Request 27", milliseconds: 74 },
{ period: "Request 28", milliseconds: 80 },
{ period: "Request 29", milliseconds: 88 },
{ period: "Request 30", milliseconds: 95 },
{ period: "Request 31", milliseconds: 100 },
{ period: "Request 32", milliseconds: 110 },
{ period: "Request 33", milliseconds: 125 },
{ period: "Request 34", milliseconds: 150 },
{ period: "Request 35", milliseconds: 180 },
{ period: "Request 36", milliseconds: 200 },
{ period: "Missing reading", milliseconds: null },
{ period: "Outside window", milliseconds: 260 },
];
const result = Chart.binHistogram(
data.map((row) => row.milliseconds),
[0, 25, 50, 100, 200],
);
const config = {
count: {
label: "Probability per ms",
color: "#733bff",
formatValue: (value) => `${Number(value).toFixed(4)} / ms`,
},
} satisfies Chart.SeriesConfig;
export function CheckoutDensityHistogram() {
return (
`${value} ms`,
}}
yAxisProps={{
axisLine: false,
tickLine: false,
tickMargin: 12,
width: 64,
tickFormatter: (value) => Number(value).toFixed(3),
}}
>
{
const bin = payload[0]?.payload as Chart.HistogramBin | undefined;
return bin
? `${bin.lower}–${bin.upper} ms${bin.upper === 200 ? " (final edge included)" : " (upper edge excluded)"}`
: "Response time";
}}
/>
{result.accepted} accepted · {result.missing} missing · {result.nonfinite} nonfinite ·{" "}
{result.outOfRange} outside 0–200 ms
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/histogram-density/README.md)
- [LICENSE](/charts/docs/examples/histogram-density/LICENSE)
## Materials
Choose plain, paper, clay or glow. Numeric bounds stay fixed; custom `shape`, `filter` or `style.filter` owns paint.
## Complete Materials consumer
Plain, paper, clay and glow on quantitative histogram rectangles.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { MaterialHistogram } from "./examples/histogram-materials/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/histogram-materials/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Request 1", milliseconds: 12 },
{ period: "Request 2", milliseconds: 18 },
{ period: "Request 3", milliseconds: 23 },
{ period: "Request 4", milliseconds: 25 },
{ period: "Request 5", milliseconds: 27 },
{ period: "Request 6", milliseconds: 30 },
{ period: "Request 7", milliseconds: 31 },
{ period: "Request 8", milliseconds: 32 },
{ period: "Request 9", milliseconds: 34 },
{ period: "Request 10", milliseconds: 35 },
{ period: "Request 11", milliseconds: 36 },
{ period: "Request 12", milliseconds: 38 },
{ period: "Request 13", milliseconds: 40 },
{ period: "Request 14", milliseconds: 42 },
{ period: "Request 15", milliseconds: 43 },
{ period: "Request 16", milliseconds: 45 },
{ period: "Request 17", milliseconds: 47 },
{ period: "Request 18", milliseconds: 49 },
{ period: "Request 19", milliseconds: 50 },
{ period: "Request 20", milliseconds: 52 },
{ period: "Request 21", milliseconds: 55 },
{ period: "Request 22", milliseconds: 58 },
{ period: "Request 23", milliseconds: 60 },
{ period: "Request 24", milliseconds: 63 },
{ period: "Request 25", milliseconds: 66 },
{ period: "Request 26", milliseconds: 70 },
{ period: "Request 27", milliseconds: 74 },
{ period: "Request 28", milliseconds: 80 },
{ period: "Request 29", milliseconds: 88 },
{ period: "Request 30", milliseconds: 95 },
{ period: "Request 31", milliseconds: 100 },
{ period: "Request 32", milliseconds: 110 },
{ period: "Request 33", milliseconds: 125 },
{ period: "Request 34", milliseconds: 150 },
{ period: "Request 35", milliseconds: 180 },
{ period: "Request 36", milliseconds: 200 },
{ period: "Missing reading", milliseconds: null },
{ period: "Outside window", milliseconds: 260 },
];
const result = Chart.binHistogram(
data.map((row) => row.milliseconds),
[0, 25, 50, 100, 200],
);
const config = {
count: {
label: "Probability per ms",
color: "#733bff",
formatValue: (value) => `${Number(value).toFixed(4)} / ms`,
},
} satisfies Chart.SeriesConfig;
export function MaterialHistogram({
material = "paper",
}: {
material?: "plain" | "paper" | "clay" | "glow";
} = {}) {
return (
`${value} ms`,
}}
yAxisProps={{
axisLine: false,
tickLine: false,
tickMargin: 12,
width: 64,
tickFormatter: (value) => Number(value).toFixed(3),
}}
>
{
const bin = payload[0]?.payload as Chart.HistogramBin | undefined;
return bin
? `${bin.lower}–${bin.upper} ms${bin.upper === 200 ? " (final edge included)" : " (upper edge excluded)"}`
: "Response time";
}}
/>
{result.accepted} accepted · {result.missing} missing · {result.nonfinite} nonfinite ·{" "}
{result.outOfRange} outside 0–200 ms
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/histogram-materials/README.md)
- [LICENSE](/charts/docs/examples/histogram-materials/LICENSE)
## API reference
Use `xAxisProps` and `yAxisProps` for tick presentation; Histogram owns quantitative axes and geometry.
### binHistogram
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `samples` | `readonly (number \| null \| undefined)[]` | Required | Raw observations; missing, nonfinite and out-of-range values are counted separately. |
| `edges` | `readonly number[]` | Required | At least two finite, strictly increasing edges with finite positive widths. Invalid edges throw. |
Returns `HistogramBinningResult`: `bins`, `accepted`, `missing`, `nonfinite` and `outOfRange`. Each bin has `lower`, `upper` and `count`. No coercion or edge inference occurs.
Pre-aggregated bins may have gaps. Bounds must be ordered, nonoverlapping and finite with positive finite widths; counts and their total must be nonnegative safe integers.
### HistogramChart
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `bins` | `readonly HistogramBin[]` | Required | Validated intervals; their first lower and final upper bounds define the horizontal domain. |
| `measure` | `"count" \| "density"` | Required | Count height or normalized probability density. Numerically unrepresentable density throws. |
| `animate` | `boolean \| BarAnimation` | `false` | Reuses chart-owned Bar entrance and reduced-motion handling. Options include `revealDurationMs`, `revealEasing` and `hoverTransition`. |
| `xAxisProps` | Native XAxis presentation props | — | Ticks, tick formatting and spacing; numeric scale, domain, axis identity and bin midpoint are owned. |
| `yAxisProps` | Native YAxis presentation props | — | Tick presentation; numeric domain is `[0, "dataMax"]`. |
| `accessibilityLayer` | `boolean` | `true` in Recharts | Arrow-key inspection; set explicitly in these components. |
| `margin` | `{ top?: number; right?: number; bottom?: number; left?: number }` | `5` each side | Plot spacing. |
| `children` | `ReactNode` | — | Explicit histogram series, grid and tooltip. |
Owns data, vertical layout, scales and spacing. See [XAxis](https://recharts.github.io/en-US/api/XAxis/), [YAxis](https://recharts.github.io/en-US/api/YAxis/) and [BarChart](https://recharts.github.io/en-US/api/BarChart/) for presentation options.
### HistogramSeries
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `seriesKey` | `string` | `"count"` | Root metadata and visibility identity; internal `dataKey` is owned. |
| `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Finish on corrected bin rectangles. |
| `shape` | `(props: HistogramShapeProps) => ReactElement` | — | Native bar shape props with corrected numeric `x`, `width` and original `bin`; custom geometry becomes consumer-owned. |
| `fill` / `stroke` | `string` | Config color / unset | Explicit paint overrides. |
| `hide` | `boolean` | `false` | Hides the series; Root visibility also applies. |
Requires `HistogramChart`. Plain bins have square corners and no minimum height. Cells/events remain available; stacking, labels, background, activeBar, bar sizing and axis identity are excluded. See [Recharts Bar](https://recharts.github.io/en-US/api/Bar/).
## Shared components
[Root](/charts/docs/chart-components/root/), [SeriesConfig](/charts/docs/chart-components/series-config/), [Tooltip](/charts/docs/chart-components/tooltip/), [Axes and grid](/charts/docs/chart-components/axes-grid/), [ResponsiveContainer](/charts/docs/chart-components/responsive-container/).
---
# Line Chart
Compose a line chart with axes, lines and tooltips.
## Complete Line Chart consumer
A single smooth series with complete monthly visitor data.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { VisitorTrendChart } from "./examples/line/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/line/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Jan", visitors: 420 },
{ period: "Feb", visitors: 580 },
{ period: "Mar", visitors: 510 },
{ period: "Apr", visitors: 760 },
{ period: "May", visitors: 680 },
{ period: "Jun", visitors: 910 },
{ period: "Jul", visitors: 840 },
{ period: "Aug", visitors: 1070 },
{ period: "Sep", visitors: 960 },
{ period: "Oct", visitors: 1180 },
{ period: "Nov", visitors: 1110 },
{ period: "Dec", visitors: 1360 },
];
const config = {
visitors: { label: "Visitors", color: "#733bff" },
} satisfies Chart.SeriesConfig;
export function VisitorTrendChart() {
return (
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/line/README.md)
- [LICENSE](/charts/docs/examples/line/LICENSE)
## Usage
Pass `data` and `config` to `LineChart`. Customize its defaults or compose plot parts as children.
`legend={{}}` enables the internally positioned `Chart.Legend`; `LineChart` owns its context and placement.
```tsx title="Imports"
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
```
```tsx title="Composition"
```
The grid guides comparison, axes label the scale, `LineSeries` draws the values, and `Tooltip` shows the inspected point. Omit explicit children and pass `xDataKey="period"` to use generated parts.
## Curve types
Choose smooth, linear or step-after interpolation. The code follows the selected curve.
## Complete Curve types consumer
Linear curve override.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { VisitorCurveChart } from "./examples/line-smooth/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/line-smooth/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Jan", visitors: 420 },
{ period: "Feb", visitors: 580 },
{ period: "Mar", visitors: 510 },
{ period: "Apr", visitors: 760 },
{ period: "May", visitors: 680 },
{ period: "Jun", visitors: 910 },
{ period: "Jul", visitors: 840 },
{ period: "Aug", visitors: 1070 },
{ period: "Sep", visitors: 960 },
{ period: "Oct", visitors: 1180 },
{ period: "Nov", visitors: 1110 },
{ period: "Dec", visitors: 1360 },
];
const config = {
visitors: { label: "Visitors", color: "#733bff" },
} satisfies Chart.SeriesConfig;
export function VisitorCurveChart({
curve = "monotone",
}: {
curve?: "monotone" | "linear" | "stepAfter";
} = {}) {
return (
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/line-smooth/README.md)
- [LICENSE](/charts/docs/examples/line-smooth/LICENSE)
## Multiple series
Compare revenue with a dashed target. Use the legend to hide either series.
## Complete Multiple series consumer
Multiple series line composition.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { RevenueComparisonChart } from "./examples/line-comparison/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/line-comparison/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Jan", actual: 42, target: 48 },
{ period: "Feb", actual: 58, target: 54 },
{ period: "Mar", actual: 51, target: 60 },
{ period: "Apr", actual: 76, target: 66 },
{ period: "May", actual: 68, target: 72 },
{ period: "Jun", actual: 91, target: 78 },
{ period: "Jul", actual: 84, target: 84 },
{ period: "Aug", actual: 107, target: 90 },
{ period: "Sep", actual: 96, target: 96 },
{ period: "Oct", actual: 118, target: 102 },
{ period: "Nov", actual: 111, target: 108 },
{ period: "Dec", actual: 136, target: 114 },
];
const config = {
actual: { label: "Revenue", color: "#733bff", formatValue: (value: unknown) => `$${value}k` },
target: { label: "Target", color: "#119548", formatValue: (value: unknown) => `$${value}k` },
} satisfies Chart.SeriesConfig;
export function RevenueComparisonChart() {
return (
`$${value}k` }}
series={[
{ seriesKey: "actual", dataKey: "actual" },
{ seriesKey: "target", dataKey: "target", type: "linear", strokeDasharray: "5 5" },
]}
/>
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/line-comparison/README.md)
- [LICENSE](/charts/docs/examples/line-comparison/LICENSE)
## Dots and labels
Explicit children add diamond markers and labels. `LineChart` still owns sizing, visibility and motion.
## Complete Dots and labels consumer
Dots and labels line composition.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { ResponseTimeChart } from "./examples/line-markers/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/line-markers/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Mon", response: 140 },
{ period: "Tue", response: 128 },
{ period: "Wed", response: 156 },
{ period: "Thu", response: 119 },
{ period: "Fri", response: 134 },
{ period: "Sat", response: 108 },
{ period: "Sun", response: 121 },
];
const config = {
response: {
label: "Response time",
color: "#733bff",
formatValue: (value: unknown) => `${value} ms`,
},
} satisfies Chart.SeriesConfig;
function Diamond({ cx, cy }: Pick) {
if (cx == null || cy == null) return ;
return (
);
}
export function ResponseTimeChart() {
return (
}>
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/line-markers/README.md)
- [LICENSE](/charts/docs/examples/line-markers/LICENSE)
## Materials
Choose plain, paper, clay or glow.
## Complete Materials consumer
Paper line composition.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { MaterialLineChart } from "./examples/line-paper/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/line-paper/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Jan", visitors: 420 },
{ period: "Feb", visitors: 580 },
{ period: "Mar", visitors: 510 },
{ period: "Apr", visitors: 760 },
{ period: "May", visitors: 680 },
{ period: "Jun", visitors: 910 },
{ period: "Jul", visitors: 840 },
{ period: "Aug", visitors: 1070 },
{ period: "Sep", visitors: 960 },
{ period: "Oct", visitors: 1180 },
{ period: "Nov", visitors: 1110 },
{ period: "Dec", visitors: 1360 },
];
const config = {
visitors: { label: "Visitors", color: "#733bff" },
} satisfies Chart.SeriesConfig;
export function MaterialLineChart({
material = "paper",
}: {
material?: "plain" | "paper" | "clay" | "glow";
} = {}) {
return (
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/line-paper/README.md)
- [LICENSE](/charts/docs/examples/line-paper/LICENSE)
## API reference
Passing `config` enables the defaults below. Omit `config` for the existing `Root` + `LineChart` composition.
### LineChart
Configured `LineChart` owns its `Root`; do not wrap it in another one. Each config key generates a series unless you provide `series` or explicit children.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `readonly Row[]` | Required | Observations to plot. |
| `config` | `SeriesConfig` | Required in configured mode | Series labels, colors and optional value formatters. |
| `xDataKey` | `DataKey` | Required with generated parts | Field or accessor for x-axis labels. Omit with explicit children. |
| `aria-label` / `aria-labelledby` | `string` | One required | Accessible chart name. Keyboard inspection instructions are provided. |
| `height` / `width` | Native chart dimensions | `280px` / `100%` | Responsive plot dimensions; `style` and `className` can also override sizing. |
| `curve` | Recharts curve type | `"monotone"` | Curve for generated series; use `"linear"` or `"stepAfter"` here. |
| `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Finish for generated series, including `"paper"`. |
| `series` | `ConfiguredLineSeries[]` | Config keys | Per-series props; each entry needs `dataKey` and a unique `seriesKey` in config. |
| `xAxis` / `yAxis` / `grid` | Native props or `false` | Styled axes and horizontal grid | Override generated parts or hide them. |
| `tooltip` / `legend` | Kind props or `false` | Enabled with generated parts | Override or hide the tooltip and legend. Explicit children need `legend={{}}` to add a legend. |
| `animate` | `boolean` or `LineAnimation` | `true` configured; `false` legacy | Entrance and emphasis motion; respects reduced motion. |
| `defaultVisibleSeries` | `readonly string[]` | All series | Initial uncontrolled selection. Legend toggles update it automatically. |
| `visibleSeries` / `onVisibleSeriesChange` | `readonly string[]` / callback | Uncontrolled | Provide both for controlled toggles. Without a callback, controlled visibility is read-only. |
| `children` | `ReactNode` | Generated parts | Explicit children replace the plot parts, including `null` or an empty fragment. |
With explicit children, omit `xDataKey`, `curve`, `material`, `series` and generated-part options. Keep visibility mode stable or remount to change it. See [Recharts LineChart](https://recharts.github.io/en-US/api/LineChart/) for native plot props.
### LineSeries
Use `LineSeries` inside explicit children, or pass its props in the configured `series` array. String `dataKey` values identify their config entry; `seriesKey` supplies a separate identity for accessor or numeric keys.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `dataKey` | `DataKey` | Required | Value field or accessor. |
| `seriesKey` | `string` | String `dataKey` | Config and visibility identity; required in a `series` array. |
| `type` | Recharts curve type | `"linear"` explicit; `"monotone"` generated | Curve for this line. |
| `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Line finish; custom `shape` or `filter` takes precedence. |
| `dot` | Native dot props or renderer | `true` explicit; radius `2.5` generated | Point markers; use `false` to hide them. |
| `strokeWidth` / `strokeDasharray` | `number` / `string` | Width `1` explicit / `2` generated; no dashes | Stroke weight and dash pattern. |
Missing values remain gaps unless `connectNulls` is enabled. See [Recharts Line](https://recharts.github.io/en-US/api/Line/) for native marks and [material support](/charts/docs/guides/materials/) for finishes.
## Shared components
[Root](/charts/docs/chart-components/root/), [SeriesConfig](/charts/docs/chart-components/series-config/), [Legend](/charts/docs/chart-components/legend/), [Tooltip](/charts/docs/chart-components/tooltip/), [Axes and grid](/charts/docs/chart-components/axes-grid/), [ResponsiveContainer](/charts/docs/chart-components/responsive-container/), [Labels and cells](/charts/docs/chart-components/labels/).
---
# Pie and Donut
Compare parts of a whole with explicit category identity and native sectors.
## Complete Pie and Donut consumer
A complete 1,000-hour team allocation with category labels and percentage labels.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { TeamAllocationChart } from "./examples/pie/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/pie/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const config = {
design: { label: "Design", color: "#733bff", formatValue: (value: unknown) => `${value} hours` },
engineering: {
label: "Engineering",
color: "#2469d4",
formatValue: (value: unknown) => `${value} hours`,
},
operations: {
label: "Operations",
color: "#b65c16",
formatValue: (value: unknown) => `${value} hours`,
},
research: {
label: "Research",
color: "#147c68",
formatValue: (value: unknown) => `${value} hours`,
},
} satisfies Chart.SeriesConfig;
const data: { key: keyof typeof config; hours: number; share: string }[] = [
{ key: "design", hours: 420, share: "42%" },
{ key: "engineering", hours: 310, share: "31%" },
{ key: "operations", hours: 170, share: "17%" },
{ key: "research", hours: 100, share: "10%" },
];
export function TeamAllocationChart({ shape = "pie" }: { shape?: "pie" | "donut" }) {
return (
String(entry.payload?.key ?? entry.name)} />
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/pie/README.md)
- [LICENSE](/charts/docs/examples/pie/LICENSE)
## Usage
Set `categoryKey` for stable slice colors and `innerRadius` for a donut.
```tsx title="Composition"
String(entry.payload?.key ?? entry.name)} />
```
Use stable keys for config, `categoryKey`, `nameKey` and `Tooltip.itemKey`. Reordering rows preserves their configured colors. Pie has no configured wrapper or separate Donut export.
Chart-owned angular entrance respects reduced motion. `animationDirection` changes the sweep, preserving final angles and data order.
## Controlled categories
Filter rows when legend state changes: `Root.visibleSeries` alone does not hide sectors. Remaining sectors use the selected total; this example allows an empty selection.
## Complete Controlled categories consumer
Consumer-owned category filtering and selected total.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { VisibleAllocationChart } from "./examples/pie-visibility/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/pie-visibility/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import { useState } from "react";
import "@kind-ui/charts/styles.css";
const config = {
design: { label: "Design", color: "#733bff", formatValue: (value: unknown) => `${value} hours` },
engineering: {
label: "Engineering",
color: "#2469d4",
formatValue: (value: unknown) => `${value} hours`,
},
operations: {
label: "Operations",
color: "#b65c16",
formatValue: (value: unknown) => `${value} hours`,
},
research: {
label: "Research",
color: "#147c68",
formatValue: (value: unknown) => `${value} hours`,
},
} satisfies Chart.SeriesConfig;
const data: { key: keyof typeof config; hours: number; share: string }[] = [
{ key: "design", hours: 420, share: "42%" },
{ key: "engineering", hours: 310, share: "31%" },
{ key: "operations", hours: 170, share: "17%" },
{ key: "research", hours: 100, share: "10%" },
];
export function VisibleAllocationChart() {
const [visible, setVisible] = useState(Object.keys(config));
const selected = data.filter((row) => visible.includes(row.key));
const total = selected.reduce((sum, row) => sum + row.hours, 0);
return (
{total.toLocaleString("en-US")} hours selected
String(entry.payload?.key ?? entry.name)} />
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/pie-visibility/README.md)
- [LICENSE](/charts/docs/examples/pie-visibility/LICENSE)
## Materials
Choose plain, paper, clay or glow. Percentage labels remain inside sectors; category names stay in the legend.
## Complete Pie materials consumer
Native donut sectors with plain, paper, clay or glow finishes.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { AllocationMaterialChart } from "./examples/pie-materials/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/pie-materials/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const config = {
design: { label: "Design", color: "#733bff", formatValue: (value: unknown) => `${value} hours` },
engineering: {
label: "Engineering",
color: "#2469d4",
formatValue: (value: unknown) => `${value} hours`,
},
operations: {
label: "Operations",
color: "#b65c16",
formatValue: (value: unknown) => `${value} hours`,
},
research: {
label: "Research",
color: "#147c68",
formatValue: (value: unknown) => `${value} hours`,
},
} satisfies Chart.SeriesConfig;
const data: { key: keyof typeof config; hours: number; share: string }[] = [
{ key: "design", hours: 420, share: "42%" },
{ key: "engineering", hours: 310, share: "31%" },
{ key: "operations", hours: 170, share: "17%" },
{ key: "research", hours: 100, share: "10%" },
];
export function AllocationMaterialChart({
material = "paper",
}: {
material?: "plain" | "paper" | "clay" | "glow";
}) {
return (
{data.map((row) => (
))}
String(entry.payload?.key ?? entry.name)} />
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/pie-materials/README.md)
- [LICENSE](/charts/docs/examples/pie-materials/LICENSE)
## API reference
Focused props for explicit Pie composition; upstream links cover native options.
### PieChart
Use inside a sized `ResponsiveContainer`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `animate` | `boolean \| PieAnimation` | `false` | Sector entrance and interaction motion; respects reduced motion. |
| `animationDirection` | `"clockwise" \| "anticlockwise"` | `"clockwise"` | Continuous entrance sweep; native geometry and row order stay unchanged. |
| `accessibilityLayer` | `boolean` | `true` in Recharts | Enables native keyboard inspection; set explicitly here. |
| `aria-label` / `aria-labelledby` | `string` | — | Accessible chart name. |
| `children` | `ReactNode` | — | Explicit PieSeries, labels and tooltip. |
`PieAnimation` accepts `revealDurationMs` (default `1000`), `revealEasing` and `hoverTransition`. Changing these or direction during entrance finishes it. Per-series animation is disabled. See [Recharts PieChart](https://recharts.github.io/en-US/api/PieChart/).
### PieSeries
Value and category identity use separate fields.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `Row[]` | — | Consumer-owned category rows. |
| `dataKey` | `string \| number \| ((row: Row) => unknown)` | `"value"` in Recharts | Numeric value field or accessor; set explicitly here. |
| `categoryKey` | `string \| ((row: Row) => string)` | Off | Resolves an own top-level field or accessor into `Root.config`; requires explicit series data and string IDs. |
| `nameKey` | `string \| number \| ((row: Row) => unknown)` | `"name"` | Native category name; use config keys for semantic identity. |
| `innerRadius` | `number` or percentage | `0` | A positive radius produces a Donut. |
| `outerRadius` | `number` or percentage | `"80%"` | Sector size; percentages use Recharts' available radius. |
| `startAngle` / `endAngle` | `number` | `0` / `360` | Native angular geometry in degrees. |
| `paddingAngle` | `number` | `0` | Gap between sectors in degrees. |
| `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Finish on default sectors; consumer rendering can take precedence. |
| `emphasisKey` | `(payload: unknown) => string \| number \| undefined` | Native name | Optional semantic sector identity for paint emphasis; does not configure tooltip identity. |
| `stroke` | `string` | `"none"` | Native sector outline. |
Explicit series fill disables category defaults; datum or Cell fill wins over them. Custom shapes retain rendering control. Filter rows explicitly; `PieSeries` has no `seriesKey`. Custom shapes, filters or transforms can bypass Kind paint. See [Recharts Pie](https://recharts.github.io/en-US/api/Pie/).
## Shared components
[Root](/charts/docs/chart-components/root/), [SeriesConfig](/charts/docs/chart-components/series-config/), [Labels and cells](/charts/docs/chart-components/labels/), [Legend](/charts/docs/chart-components/legend/), [Tooltip](/charts/docs/chart-components/tooltip/), [ResponsiveContainer](/charts/docs/chart-components/responsive-container/).
---
# Radar Chart
Compare two product profiles across six dimensions on a shared numeric scale.
## Complete Radar Chart consumer
Six comparable research dimensions for two products on a fixed 0–100 scale.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { ProductRadarChart } from "./examples/radar/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/radar/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Speed", studio: 88, field: 62 },
{ period: "Quality", studio: 76, field: 91 },
{ period: "Value", studio: 63, field: 84 },
{ period: "Support", studio: 92, field: 68 },
{ period: "Reach", studio: 58, field: 89 },
{ period: "Trust", studio: 81, field: 74 },
];
const config = {
studio: { label: "Studio", color: "#733bff" },
field: { label: "Field", color: "#008b82" },
} satisfies Chart.SeriesConfig;
export function ProductRadarChart() {
return (
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/radar/README.md)
- [LICENSE](/charts/docs/examples/radar/LICENSE)
## Usage
Compare measures on a shared radial scale with one `RadarSeries` per profile.
```tsx title="Composition"
```
Use comparable units and a fixed domain across spokes. These research scores share a 0–100 scale.
## Center-out motion
`animate` reveals the polygon from its center. Interaction or data/layout changes finish it; reduced motion skips it. Custom shapes, masks, filters or transforms can bypass the reveal.
## Series selection
Click a polygon or press Enter/Space on its series control to select it. Repeat to clear; Escape clears selection. Values and visibility stay unchanged.
## Complete Series selection consumer
Controlled persistent selection, independent of native spoke inspection.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { SelectableRadarChart } from "./examples/radar-selection/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/radar-selection/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import { useState } from "react";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Speed", studio: 88, field: 62 },
{ period: "Quality", studio: 76, field: 91 },
{ period: "Value", studio: 63, field: 84 },
{ period: "Support", studio: 92, field: 68 },
{ period: "Reach", studio: 58, field: 89 },
{ period: "Trust", studio: 81, field: 74 },
];
const config = {
studio: { label: "Studio", color: "#733bff" },
field: { label: "Field", color: "#008b82" },
} satisfies Chart.SeriesConfig;
export function SelectableRadarChart() {
const [selectedSeries, setSelectedSeries] = useState(null);
return (
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/radar-selection/README.md)
- [LICENSE](/charts/docs/examples/radar-selection/LICENSE)
Enable `selection="series"`. Omit `selectedSeries` for local state, or pair it with `onSelectedSeriesChange` for controlled selection.
Selection dims paint, not spoke tooltip values. Keep Root emphasis at `"auto"`; visibility and persistent selection remain separate.
SVG series controls expose labels and pressed state, but provide no arrow-key spoke navigation. Keep the accessible data alternative and verify support in your target assistive technologies.
## Materials
Choose plain, paper, clay or glow. Glow’s exterior halo is decorative, not a score boundary.
## Complete Materials consumer
Plain, paper, clay and glow on native radar polygons.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { MaterialRadarChart } from "./examples/radar-materials/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/radar-materials/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Speed", studio: 88, field: 62 },
{ period: "Quality", studio: 76, field: 91 },
{ period: "Value", studio: 63, field: 84 },
{ period: "Support", studio: 92, field: 68 },
{ period: "Reach", studio: 58, field: 89 },
{ period: "Trust", studio: 81, field: 74 },
];
const config = {
studio: { label: "Studio", color: "#733bff" },
field: { label: "Field", color: "#008b82" },
} satisfies Chart.SeriesConfig;
export function MaterialRadarChart({
material = "paper",
}: {
material?: "plain" | "paper" | "clay" | "glow";
} = {}) {
return (
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/radar-materials/README.md)
- [LICENSE](/charts/docs/examples/radar-materials/LICENSE)
Custom `shape`, `filter` or `style.filter` overrides the finish. Dots and labels retain native rendering. See [material support](/charts/docs/guides/materials/).
## API reference
Focused props for explicit Radar composition; upstream links cover native geometry.
### RadarChart
Use inside `Root` and a sized `ResponsiveContainer`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `Row[]` | — | Category rows used by explicit axes and series. |
| `animate` | `boolean \| RadarAnimation` | `false` | Center-out entrance for default polygons; respects reduced motion. |
| `selection` | `"none" \| "series"` | `"none"` | Opts in to persistent series controls. |
| `selectedSeries` | `string \| null` | Local state | Controlled selection when supplied; requires a callback. |
| `onSelectedSeriesChange` | `(next: string \| null) => void` | — | User toggle/reset requests; does not control visibility. |
| `cx` / `cy` | `number \| string` | `"50%"` | Native polar center. |
| `outerRadius` | `number \| string` | `"80%"` | Plot radius; these components reserve label space with `"68%"`. |
| `aria-label` / `aria-labelledby` | `string` | — | Accessible plot name; does not add spoke keyboard inspection. |
| `children` | `ReactNode` | — | Explicit polar axes, grid, series and tooltip. |
Entrance runs once per mount. Unavailable uncontrolled selections clear without a callback; controlled values remain consumer-owned and show no selected paint while unavailable.
`RadarAnimation` accepts `revealDurationMs` (default `1000`), `revealEasing` (default `"easeOut"`) and `hoverTransition` (shared spring). See [Recharts RadarChart](https://recharts.github.io/en-US/api/RadarChart/).
### RadarSeries
String `dataKey` values identify config entries. Use `seriesKey` for numeric keys or accessors with visibility or selection.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `dataKey` | `string \| number \| ((row: Row) => unknown)` | — | Value field or native accessor; supplied for both profiles here. |
| `seriesKey` | `string` | String `dataKey` | Metadata, visibility and selection identity. |
| `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Finish on default polygons; consumer rendering takes precedence. |
| `stroke` / `fill` | `string` | Config color | Override outline or fill independently. |
| `fillOpacity` | `number` | SVG default `1` | These overlapping profiles explicitly use `0.18`. |
| `dot` | `boolean` or native dot props / renderer | `false` | Optional native category marks. |
| `activeDot` | `boolean` or native dot props / renderer | Kind active marker | Native inspected-category marker; independent of selection. |
| `hide` | `boolean` | `false` | Hides the series; Root visibility also applies. |
| `shape` | Native element or renderer | Kind default polygon | Consumer-owned shape bypasses material and center-out masking. |
Per-series animation is disabled. Recharts exposes no Radar series component ref; attach refs to custom marks. See [Recharts Radar](https://recharts.github.io/en-US/api/Radar/).
## Shared components
[Root](/charts/docs/chart-components/root/), [SeriesConfig](/charts/docs/chart-components/series-config/), [Axes and grid](/charts/docs/chart-components/axes-grid/), [Legend](/charts/docs/chart-components/legend/), [Tooltip](/charts/docs/chart-components/tooltip/), [ResponsiveContainer](/charts/docs/chart-components/responsive-container/).
---
# Radial Chart
Show completion and capacity with concentric rings, gauges and stacked arcs.
## Complete Radial Chart consumer
Three project stages share an explicit 0–100 percent scale.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { ProjectProgressChart } from "./examples/radial/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/radial/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Design", label: "Design 92%", progress: 92 },
{ period: "Build", label: "Build 76%", progress: 76 },
{ period: "Review", label: "Review 58%", progress: 58 },
];
const config = {
Design: { label: "Design", color: "#733bff", formatValue: (value: unknown) => `${value}%` },
Build: { label: "Build", color: "#008b83", formatValue: (value: unknown) => `${value}%` },
Review: { label: "Review", color: "#b85c13", formatValue: (value: unknown) => `${value}%` },
} satisfies Chart.SeriesConfig;
export function ProjectProgressChart() {
return (
}
/>
String(entry.payload?.period ?? entry.name)} />
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/radial/README.md)
- [LICENSE](/charts/docs/examples/radial/LICENSE)
## Activity rings
`ActivityRings` supplies context, axes, tracks, tooltip and a static legend for progress data. Give each ring a config key, original value and optional domain. Name the chart with `aria-label` or `aria-labelledby`.
## Complete Activity rings consumer
Three original values use their own domains; tracks, tooltip and static legend come from ActivityRings.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { DailyActivityChart } from "./examples/radial-activity/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/radial-activity/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [
{ key: "move", value: 350, domain: [0, 500] as const },
{ key: "exercise", value: 30, domain: [0, 60] as const },
{ key: "stand", value: 9, domain: [0, 12] as const },
];
const config = {
move: { label: "Move", color: "#e02266", formatValue: (value: unknown) => `${value} kcal` },
exercise: {
label: "Exercise",
color: "#327448",
formatValue: (value: unknown) => `${value} min`,
},
stand: { label: "Stand", color: "#2469d4", formatValue: (value: unknown) => `${value} hours` },
} satisfies Chart.SeriesConfig;
export function DailyActivityChart() {
return ;
}
```
Complete setup files:
- [README.md](/charts/docs/examples/radial-activity/README.md)
- [LICENSE](/charts/docs/examples/radial-activity/LICENSE)
Values normalize to 0–100 for geometry; finite out-of-domain values clamp visually. The default tooltip keeps original values and config formatters. Custom native payloads expose `rawValue` and `progress`; native sector `value` is normalized. Ring order follows the array, inner to outer.
The default is responsive width, 300px height, a clockwise full sweep, rounded tracks, static legend, labels off and motion off. Do not wrap this helper in another `Root`. Use explicit composition below for custom axes/data, children or inter-chart choreography.
## Usage
`Root` supplies metadata; `RadialBarChart` owns motion. Native polar axes map values and categories to angles and rings.
```tsx title="Composition"
} />
String(entry.payload?.period ?? entry.name)} />
```
Each category resolves its config color through `categoryKey`. Values still come from the progress series; tooltip identity uses the category field too. Arc labels hide when they cannot fit.
## Capacity gauge
Compose a gauge with `RadialBarChart`; there is no `Gauge` export. `[0, 100]` and angles `180` to `0` produce a semicircle; native `Label` supplies the center summary.
## Complete Capacity gauge consumer
A semicircular gauge uses the exported RadialBar components with consumer-owned center text.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { StorageGaugeChart } from "./examples/radial-gauge/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/radial-gauge/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [{ period: "Storage", used: 72 }];
const config = {
used: {
label: "Used storage",
color: "#008b83",
formatValue: (value: unknown) => `${value} of 100 GB`,
},
} satisfies Chart.SeriesConfig;
export function StorageGaugeChart({
direction = "clockwise",
}: {
direction?: "clockwise" | "anticlockwise";
}) {
return (
{
if (!viewBox || !("cx" in viewBox)) return null;
return (
72 GB
of 100 GB used
);
}}
/>
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/radial-gauge/README.md)
- [LICENSE](/charts/docs/examples/radial-gauge/LICENSE)
Choose the entrance sweep beside Preview/Code. The example remounts to replay it; final angles, domain and track stay fixed.
## Stacked capacity
Matching `stackId` values share one ring. Committed and reserved total 86 of 100 hours. Legend toggles recalculate the stack while preserving the fixed domain.
## Complete Stacked capacity consumer
64 committed and 22 reserved hours stack on a fixed 100-hour scale.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { TeamCapacityChart } from "./examples/radial-stacked/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/radial-stacked/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
import { useState } from "react";
const data = [{ period: "October", committed: 64, reserved: 22 }];
const config = {
committed: {
label: "Committed",
color: "#733bff",
formatValue: (value: unknown) => `${value} hours`,
},
reserved: {
label: "Reserved",
color: "#008b83",
formatValue: (value: unknown) => `${value} hours`,
},
} satisfies Chart.SeriesConfig;
export function TeamCapacityChart() {
const [visible, setVisible] = useState(["committed", "reserved"]);
return (
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/radial-stacked/README.md)
- [LICENSE](/charts/docs/examples/radial-stacked/LICENSE)
## API reference
Focused props for explicit polar composition. Data, tracks and custom center labels remain consumer-owned.
### ActivityRings
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `rings` | `readonly ActivityRing[]` | Required | `{ key: string; value: number; domain?: readonly [number, number]; cellProps?: ComponentProps }`; unique keys must exist in config. Empty arrays are valid. |
| `config` | `SeriesConfig` | Required | Labels, colors, icons and original-value formatters. |
| `aria-label` / `aria-labelledby` | `string` | One required | Names the chart; original-value descriptions are supplied automatically. |
| `domain` | `readonly [number, number]` | `[0, 100]` | Default finite increasing bounds; ring-specific domains override it. |
| `responsive` / `height` | `boolean` / `number \| percentage` | `true` / `300` | Native responsive chart sizing; width defaults to 100%. |
| `animate` | `boolean \| RadialBarAnimation` | `false` | Same entrance options as RadialBarChart; respects reduced motion. |
| `series` | Native RadialBarSeries options | Tracks and rounded ends | Override series paint, background or geometry; explicit fill wins over config. |
| `labels` | `false \| LabelList options` | `false` | Optional arc labels; custom content retains control. |
| `legend` / `tooltip` | `false` or component options | Static legend / original-value tooltip | Disable either independently or supply custom rendering options. |
Native chart props override center, radii, angles, gaps and motion. Ring `cellProps` retains paint/handlers and wins over series fill. Custom paint may differ from legend metadata; update config or supply custom/disabled legend content.
### RadialBarChart
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `Row[]` | — | Categories and numeric values; each row can create a separate ring. |
| `categoryKey` | `string \| ((row: Row) => string)` | Off | Resolves chart rows into config keys for default category paint. |
| `animate` | `boolean \| RadialBarAnimation` | `false` | Chart-owned entrance; respects reduced motion. Options include `revealDurationMs` and `revealEasing`. |
| `animationDirection` | `"clockwise" \| "anticlockwise"` | `"clockwise"` | Entrance direction only; preserves final native geometry. |
| `startAngle` / `endAngle` | `number` | `0` / `360` in Recharts | Native angular extent in degrees. Examples explicitly set both. |
| `innerRadius` / `outerRadius` | `number \| string` | `"0%"` / `"80%"` in Recharts | Pixels or percentages of the native maximum radius. |
| `cx` / `cy` | `number \| string` | `"50%"` | Native center position; the gauge lowers `cy` for its semicircle. |
| `barCategoryGap` | `number \| string` | `"10%"` in Recharts | Space between category bands. |
| `aria-label` | `string` | — | Accessible plot name; include a separate data alternative. |
Changing direction after reveal does not replay it. Custom sectors or transformed marks bypass directional reveal. Reduced motion shows final geometry.
See [Recharts RadialBarChart](https://recharts.github.io/en-US/api/RadialBarChart/).
### RadialBarSeries
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `dataKey` | `string \| number \| ((row: Row) => unknown)` | Required | Value field or accessor. |
| `seriesKey` | `string` | String `dataKey` | Config and visibility identity; needed for controlled accessor keys. |
| `background` | `boolean \| object \| ReactElement \| function` | `false` | `true` supplies a pale Kind track using `--kind-ui-radial-track`, fallback `#f1f1f1`; explicit background props/renderers own the track. |
| `stackId` | `string \| number` | — | Matching IDs stack series in one category band. |
| `cornerRadius` | `number \| string` | `0` in Recharts | Native rounded sector corners. |
| `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Native mark finish; custom shapes, active shapes and filters retain rendering ownership. |
| `fill` | `string` | Config color | Series paint; native Cells can override individual rows. |
| `hide` | `boolean` | `false` | Explicit hiding; Root visibility also applies. |
Per-series animation is disabled. These examples use plain paint without automatic radial inspection emphasis. See [Recharts RadialBar](https://recharts.github.io/en-US/api/RadialBar/).
### RadialBarLabel
Use as `LabelList` content for sector-aware arc text. Center summaries use native `Label`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `show` | `boolean` | `true` | Independently hides visual text. |
| `fontSize` | `number` | `11` | Requested SVG pixel size, reduced to fit the ring band. |
| `minFontSize` | `number` | `9` | Minimum fit size; unfitting measured glyph bounds hide the text. |
| `padding` | `number` | `2` | Space at arc endpoints and band edges. |
| `fill` | `string` | `"white"` | Text paint. |
| `formatter` | Native label formatter | — | Formats the value provided by `LabelList`. |
## Shared components
[Root](/charts/docs/chart-components/root/), [SeriesConfig](/charts/docs/chart-components/series-config/), [Axes and grid](/charts/docs/chart-components/axes-grid/), [Labels and cells](/charts/docs/chart-components/labels/), [Legend](/charts/docs/chart-components/legend/), [Tooltip](/charts/docs/chart-components/tooltip/), [ResponsiveContainer](/charts/docs/chart-components/responsive-container/).
---
# Sankey Chart
Trace quantities through branching and recombining flows.
## Complete Configured flows consumer
One node-ID metadata map supplies default node/source-link colors and a static legend.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { ConfiguredFlowChart } from "./examples/sankey-config/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/sankey-config/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
import { Tooltip } from "recharts";
const data = {
nodes: [
{ id: "solar", name: "Solar" },
{ id: "wind", name: "Wind" },
{ id: "homes", name: "Homes" },
{ id: "industry", name: "Industry" },
],
links: [
{ id: "solar-homes", source: "solar", target: "homes", value: 35 },
{ id: "solar-industry", source: "solar", target: "industry", value: 25 },
{ id: "wind-homes", source: "wind", target: "homes", value: 15 },
{ id: "wind-industry", source: "wind", target: "industry", value: 25 },
],
} satisfies Chart.SankeyFlowData;
const nodeConfig = {
solar: { label: "Solar", color: "#d29319" },
wind: { label: "Wind", color: "#159c91" },
homes: { label: "Homes", color: "#8865ce" },
industry: { label: "Industry", color: "#447fd1" },
} satisfies Chart.SankeyNodeConfig;
export function ConfiguredFlowChart() {
return (
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/sankey-config/README.md)
- [LICENSE](/charts/docs/examples/sankey-config/LICENSE)
## Usage
`SankeyChart` validates balanced directed flows and delegates layout to Recharts. Share `nodeConfig` with `SankeyLegend` for default node/source-link colors and static metadata.
```tsx title="Composition"
```
Sankey needs no `Root` or axes. `nodeConfig` maps every node ID, including isolated/zero-only nodes, to a label and color. Default nodes use their own color; solid links use the source color. Import `Tooltip` from **Recharts**.
## Identities and selection
Compose native node/link callbacks when labels, gradients or interaction need custom rendering.
## Complete Energy allocation consumer
180 MWh across nine nodes, three stages and eighteen balanced flows.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { EnergyFlowChart } from "./examples/sankey/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/sankey/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
import { useState } from "react";
import { Tooltip } from "recharts";
const data = {
nodes: [
{ id: "solar", name: "Solar" },
{ id: "wind", name: "Wind" },
{ id: "hydro", name: "Hydro" },
{ id: "north", name: "North" },
{ id: "central", name: "Central" },
{ id: "south", name: "South" },
{ id: "homes", name: "Homes" },
{ id: "industry", name: "Industry" },
{ id: "services", name: "Services" },
],
links: [
{ id: "solar-north", source: "solar", target: "north", value: 30 },
{ id: "solar-central", source: "solar", target: "central", value: 20 },
{ id: "solar-south", source: "solar", target: "south", value: 10 },
{ id: "wind-north", source: "wind", target: "north", value: 20 },
{ id: "wind-central", source: "wind", target: "central", value: 30 },
{ id: "wind-south", source: "wind", target: "south", value: 30 },
{ id: "hydro-north", source: "hydro", target: "north", value: 20 },
{ id: "hydro-central", source: "hydro", target: "central", value: 10 },
{ id: "hydro-south", source: "hydro", target: "south", value: 10 },
{ id: "north-homes", source: "north", target: "homes", value: 35 },
{ id: "north-industry", source: "north", target: "industry", value: 25 },
{ id: "north-services", source: "north", target: "services", value: 10 },
{ id: "central-homes", source: "central", target: "homes", value: 15 },
{ id: "central-industry", source: "central", target: "industry", value: 30 },
{ id: "central-services", source: "central", target: "services", value: 15 },
{ id: "south-homes", source: "south", target: "homes", value: 10 },
{ id: "south-industry", source: "south", target: "industry", value: 15 },
{ id: "south-services", source: "south", target: "services", value: 25 },
],
} satisfies Chart.SankeyFlowData;
const nodeConfig = {
solar: { label: "Solar", color: "#d29319" },
wind: { label: "Wind", color: "#159c91" },
hydro: { label: "Hydro", color: "#447fd1" },
north: { label: "North", color: "#8865ce" },
central: { label: "Central", color: "#d27559" },
south: { label: "South", color: "#569951" },
homes: { label: "Homes", color: "#d29319" },
industry: { label: "Industry", color: "#447fd1" },
services: { label: "Services", color: "#159c91" },
} satisfies Chart.SankeyNodeConfig;
const names = new Map(data.nodes.map((node) => [node.id, node.name]));
export function EnergyFlowChart() {
const [active, setActive] = useState(null);
const selected = data.links.find((link) => link.id === active);
return (
Generation
Balancing zones
End use
(
{props.payload.name}
)}
link={(props) => (
setActive(active === props.payload.id ? null : props.payload.id),
onKeyDown: (event) => {
if (event.key === "Escape") setActive(null);
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
setActive(active === props.payload.id ? null : props.payload.id);
}
},
}}
/>
)}
>
`${value} MWh`} />
{selected
? `${names.get(selected.source)} → ${names.get(selected.target)}: ${selected.value} MWh`
: "180 MWh · Select a ribbon to inspect its route."}
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/sankey/README.md)
- [LICENSE](/charts/docs/examples/sankey/LICENSE)
Give nodes and links unique nonempty string IDs. Endpoints accept IDs or original-array integer indices; numeric-looking strings remain IDs. Use renderer `payload.id` for stable application identity.
Click or press Enter/Space to select a ribbon; Escape clears it. Selection changes paint, not quantities. A hidden `SankeyTable` retains all routes; narrow diagrams scroll horizontally.
Intermediate inflow must equal outflow. Model losses/gains as explicit boundary links. This example carries 180 MWh per stage.
## Flow finishes
Choose plain, paper, clay or glow with `finish`; `material="gradient"` resolves source and target colors from the same `nodeConfig`. Explicit `color` and `targetColor` remain overrides. Labels remain consumer-owned outside the filters.
## Complete Flow finishes consumer
Plain, paper, clay and glow on native gradient flow geometry.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { EnergyFlowFinishChart } from "./examples/sankey-finishes/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/sankey-finishes/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
import { useState } from "react";
import { Tooltip } from "recharts";
const data = {
nodes: [
{ id: "solar", name: "Solar" },
{ id: "wind", name: "Wind" },
{ id: "hydro", name: "Hydro" },
{ id: "north", name: "North" },
{ id: "central", name: "Central" },
{ id: "south", name: "South" },
{ id: "homes", name: "Homes" },
{ id: "industry", name: "Industry" },
{ id: "services", name: "Services" },
],
links: [
{ id: "solar-north", source: "solar", target: "north", value: 30 },
{ id: "solar-central", source: "solar", target: "central", value: 20 },
{ id: "solar-south", source: "solar", target: "south", value: 10 },
{ id: "wind-north", source: "wind", target: "north", value: 20 },
{ id: "wind-central", source: "wind", target: "central", value: 30 },
{ id: "wind-south", source: "wind", target: "south", value: 30 },
{ id: "hydro-north", source: "hydro", target: "north", value: 20 },
{ id: "hydro-central", source: "hydro", target: "central", value: 10 },
{ id: "hydro-south", source: "hydro", target: "south", value: 10 },
{ id: "north-homes", source: "north", target: "homes", value: 35 },
{ id: "north-industry", source: "north", target: "industry", value: 25 },
{ id: "north-services", source: "north", target: "services", value: 10 },
{ id: "central-homes", source: "central", target: "homes", value: 15 },
{ id: "central-industry", source: "central", target: "industry", value: 30 },
{ id: "central-services", source: "central", target: "services", value: 15 },
{ id: "south-homes", source: "south", target: "homes", value: 10 },
{ id: "south-industry", source: "south", target: "industry", value: 15 },
{ id: "south-services", source: "south", target: "services", value: 25 },
],
} satisfies Chart.SankeyFlowData;
const nodeConfig = {
solar: { label: "Solar", color: "#d29319" },
wind: { label: "Wind", color: "#159c91" },
hydro: { label: "Hydro", color: "#447fd1" },
north: { label: "North", color: "#8865ce" },
central: { label: "Central", color: "#d27559" },
south: { label: "South", color: "#569951" },
homes: { label: "Homes", color: "#d29319" },
industry: { label: "Industry", color: "#447fd1" },
services: { label: "Services", color: "#159c91" },
} satisfies Chart.SankeyNodeConfig;
const names = new Map(data.nodes.map((node) => [node.id, node.name]));
export function EnergyFlowFinishChart({ finish = "paper" }: { finish?: Chart.SankeyFinish }) {
const [active, setActive] = useState(null);
const selected = data.links.find((link) => link.id === active);
return (
Generation
Balancing zones
End use
(
{props.payload.name}
)}
link={(props) => (
setActive(active === props.payload.id ? null : props.payload.id),
onKeyDown: (event) => {
if (event.key === "Escape") setActive(null);
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
setActive(active === props.payload.id ? null : props.payload.id);
}
},
}}
/>
)}
>
`${value} MWh`} />
{selected
? `${names.get(selected.source)} → ${names.get(selected.target)}: ${selected.value} MWh`
: "180 MWh · Select a ribbon to inspect its route."}
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/sankey-finishes/README.md)
- [LICENSE](/charts/docs/examples/sankey-finishes/LICENSE)
## Motion and geometry
Entrance reveals supported link paint along fixed paths; widths and node positions stay unchanged. Interaction, data/layout/renderer changes or resize finish it; reduced motion skips it.
Recharts owns coordinates, control points and widths. Kind preserves computed geometry against presentation overrides. Small frames show a status when the padding budget cannot fit.
## API reference
Focused public props; native SVG presentation and Recharts layout options remain available.
### SankeyChart
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `SankeyFlowData` | Required | Immutable nodes and links; validates identities, quantities, acyclicity and intermediate balance. |
| `nodeConfig` | `SankeyNodeConfig` | Off | Explicit node-ID labels/colors; enables default Kind node and source-link paint. |
| `node` | Native object, element or `(props: SankeyNodeProps) => ReactNode` | Kind node with `nodeConfig`; otherwise native | Receives computed geometry and typed node identity. |
| `link` | Native object, element or native-compatible callback using `SankeyLinkProps` | Kind link with `nodeConfig`; otherwise native | Metadata enables default Kind paint/motion; custom callbacks retain control. |
| `animate` | `boolean \| { revealDurationMs?: number }` | `false` | Flow paint reveal; `true` uses 450ms. Duration must be finite and nonnegative. |
| `empty` | `ReactNode` | `"No positive flows"` | Status for empty or all-zero data. |
| `nodeWidth` | `number` | `10` | Native node rectangle width. |
| `nodePadding` | `number` | `10` | Native vertical gap between nodes. |
| `sort` | `boolean` | `true` | Native node ordering; these components use `false`. |
| `linkCurvature` | `number` | `0.5` | Native cubic link curvature. |
| `iterations` | `number` | `32` | Native layout relaxation iterations. |
| `margin` | `{ top?: number; right?: number; bottom?: number; left?: number }` | `5` each side | Reserve space for consumer labels. |
| `onClick` / `onMouseEnter` / `onMouseLeave` | `(item, type, event) => void` | — | Native event with typed node/link IDs; `type` is `"node" \| "link"`. |
| `children` | `ReactNode` | — | Native tooltip or other supported children. |
Equal-value positive parallel links are rejected; distinct values are supported. Rescale extreme numeric ranges before native layout. See [Recharts Sankey](https://recharts.github.io/en-US/api/Sankey/).
### SankeyNode
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| Native renderer geometry and `payload` | `SankeyNodeProps` | Required | Spread the complete callback props; computed rectangle dimensions win. |
| `color` | `string` | Config color; fallback `"#4f46e5"` | Explicit paint overrides metadata. |
| `finish` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Static surface filter; explicit filters take precedence. |
| `rectProps` | `SVGProps` | — | Presentation, semantics and handlers on the original rectangle. |
Supply node labels as sibling SVG text. Selection and legend state remain consumer-owned.
### SankeyLink
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| Native renderer geometry and `payload` | `SankeyLinkProps` | Required | Spread the complete callback props; path and flow width stay native. |
| `material` | `"solid" \| "gradient"` | `"solid"` | Single-color stroke or source-to-target gradient. |
| `color` | `string` | Source config color; fallback `"#4f46e5"` | Explicit source paint override. |
| `targetColor` | `string` | Target config color; fallback `"#06b6d4"` | Explicit destination override for gradient paint. |
| `finish` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Static treatment independent of material and flow width. |
| `pathProps` | `SVGProps` | — | Presentation, keyboard handlers and semantics on the original path; computed geometry wins. |
Explicit filters bypass finishes; transformed paths bypass reveal. Custom renderers own motion. Glow changes neither quantities nor hit targets.
### SankeyLegend
A standalone static list; share the exact config passed to `SankeyChart`. Config insertion order determines legend order. It does not toggle or infer flows.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `config` | `SankeyNodeConfig` | Required | Explicit node-ID labels/colors; extra entries remain visible in the legend. |
| `children` | `(item: { key: string; label: string; color: string; marker: ReactNode }) => ReactNode` | Label and marker | Custom legend content; native list attributes and refs remain available. |
### SankeyTable
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `SankeyFlowData` | Required | Complete input, including zero flows; validated independently. |
| `caption` | `string` | Required | Accessible table name. |
| `formatValue` | `(value: number) => string` | `String` | Consumer-owned units. |
| `onInspect` | `(link: SankeyFlowLink) => void` | — | Adds native inspection buttons receiving the original link. |
| `activeLinkId` | `string \| null` | — | Controlled selected row and button `aria-pressed`. |
Native attributes/refs remain available. These examples hide the table; `onInspect` supports a visible keyboard inspector.
### Flow data
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `nodes` | `readonly SankeyFlowNode[]` | Required | Each node has unique `id: string` and `name: string`; extra metadata is retained. |
| `links` | `readonly SankeyFlowLink[]` | Required | Each link has unique `id: string`, endpoints and a value. |
| `source` / `target` | `string \| number` | Required | Node ID or original node-array integer index. |
| `value` | `number` | Required | Finite nonnegative flow quantity. |
`prepareSankeyData(data)` validates/copies input and resolves endpoints to indices. It does not aggregate, normalize or infer flows. Cycles, invalid endpoints/IDs and negative/nonfinite quantities throw.
Zero links remain in the table but leave plotted geometry; isolated nodes also leave the plot.
## Shared components
[ResponsiveContainer](/charts/docs/chart-components/responsive-container/), [Tooltip](/charts/docs/chart-components/tooltip/).
Sankey uses the native Recharts Tooltip for hover inspection. Path handlers and status text provide keyboard selection. Give the diagram a measurable height; these examples use a 540px minimum width.
---
# Scatter and Bubble Chart
Compare numeric relationships across series and encode a third measure with bubble area.
## Complete Scatter Chart consumer
Eight task observations on numeric latency and acceptance axes.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { TaskScatterChart } from "./examples/scatter/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/scatter/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import { useState } from "react";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Search (weekday)", cohort: "weekday", latency: 28, acceptance: 84 },
{ period: "Draft (weekday)", cohort: "weekday", latency: 52, acceptance: 92 },
{ period: "Summarize (weekday)", cohort: "weekday", latency: 41, acceptance: 89 },
{ period: "Translate (weekday)", cohort: "weekday", latency: 65, acceptance: 95 },
{ period: "Search (weekend)", cohort: "weekend", latency: 35, acceptance: 80 },
{ period: "Draft (weekend)", cohort: "weekend", latency: 61, acceptance: 88 },
{ period: "Summarize (weekend)", cohort: "weekend", latency: 47, acceptance: 86 },
{ period: "Translate (weekend)", cohort: "weekend", latency: 76, acceptance: 93 },
];
const weekdayShape = "circle";
const weekendShape = "diamond";
const config = {
weekday: { label: "Weekday", color: "#733bff", legendShape: weekdayShape },
weekend: { label: "Weekend", color: "#07948b", legendShape: weekendShape },
} satisfies Chart.SeriesConfig;
export function TaskScatterChart() {
const [visibleSeries, setVisibleSeries] = useState(["weekday", "weekend"]);
return (
row.cohort === "weekday")}
shape={weekdayShape}
/>
row.cohort === "weekend")}
shape={weekendShape}
/>
pointLabel={(row) => row.period} />
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/scatter/README.md)
- [LICENSE](/charts/docs/examples/scatter/LICENSE)
## Usage
Supply records to each `ScatterSeries` and numeric keys to the X and Y axes.
```tsx title="Composition"
row.cohort === "weekday")} shape="circle" />
row.cohort === "weekend")} shape="diamond" />
pointLabel={(row) => row.period} />
```
Series keys identify legend entries; X/Y keys identify dimensions. Reuse each symbol in `legendShape` and series `shape`; legend toggles use consumer-owned visibility.
## Bubble size
Add `ZAxis` to compose bubbles; there is no `BubbleChart` export. Its domain and range map request volume to symbol area.
## Complete Bubble size consumer
Request volume adds a third dimension, including raw zero and missing measurements.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { TaskBubbleChart } from "./examples/scatter-bubble/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/scatter-bubble/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Search", latency: 28, acceptance: 84, requests: 260 },
{ period: "Draft", latency: 52, acceptance: 92, requests: 180 },
{ period: "Summarize", latency: 41, acceptance: 89, requests: 120 },
{ period: "Translate", latency: 65, acceptance: 95, requests: 75 },
{ period: "Classify", latency: 19, acceptance: 81, requests: 0 },
{ period: "Extract", latency: 73, acceptance: 88, requests: null },
];
const config = {
tasks: { label: "Tasks", color: "#733bff", legendShape: "circle" },
} satisfies Chart.SeriesConfig;
export function TaskBubbleChart() {
return (
pointLabel={(row) => row.period}
zDimension={{ dataKey: "requests", name: "Requests", unit: "k" }}
/>
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/scatter-bubble/README.md)
- [LICENSE](/charts/docs/examples/scatter-bubble/LICENSE)
Native Z scaling uses the minimum area for zero and missing values. `zDimension` restores their raw tooltip values without changing geometry.
Use top-level keys or a shared accessor for nested Z values. Validate negative count inputs before scaling; Kind retains native size behavior.
## Materials
Choose plain, paper, clay or glow.
## Complete Materials consumer
Four finishes preserve native bubble geometry.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { MaterialBubbleChart } from "./examples/scatter-materials/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/scatter-materials/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [
{ period: "Search", latency: 28, acceptance: 84, requests: 260 },
{ period: "Draft", latency: 52, acceptance: 92, requests: 180 },
{ period: "Summarize", latency: 41, acceptance: 89, requests: 120 },
{ period: "Translate", latency: 65, acceptance: 95, requests: 75 },
{ period: "Classify", latency: 19, acceptance: 81, requests: 0 },
{ period: "Extract", latency: 73, acceptance: 88, requests: null },
];
const config = {
tasks: { label: "Tasks", color: "#733bff", legendShape: "circle" },
} satisfies Chart.SeriesConfig;
export function MaterialBubbleChart({ material = "paper" }: { material?: Chart.ScatterMaterial }) {
return (
pointLabel={(row) => row.period}
zDimension={{ dataKey: "requests", name: "Requests", unit: "k" }}
/>
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/scatter-materials/README.md)
- [LICENSE](/charts/docs/examples/scatter-materials/LICENSE)
Finishes preserve symbol paths and Z area; glow is decorative. Custom shapes, point filters or clip paths bypass the finish. Small marks may make finishes indistinguishable.
## Interaction and accessibility
Pointer inspection selects visible point records. Left/Right visits the first registered series in data order, not spatial order or every series. Escape dismisses inspection.
Motion fades marks in place. Interaction or data/visibility/geometry changes finish entrance; reduced motion disables it. Remount to replay.
## API reference
Focused props for explicit numeric composition; Recharts owns scales, symbols and point selection.
### ScatterChart
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `animate` | `boolean \| ScatterAnimation` | `false` | In-place entrance fade and shared tooltip motion; respects reduced motion. |
| `accessibilityLayer` | `boolean` | `true` in Recharts | Native first-series keyboard inspection; set explicitly here. |
| `margin` | `{ top?: number; right?: number; bottom?: number; left?: number }` | `5` each | Plot spacing for axes and marks. |
| `children` | `ReactNode` | — | Explicit axes, grid, series and Scatter tooltip. |
| `aria-label` / `aria-labelledby` | `string` | — | Accessible plot name. |
No configured Line shortcuts. See [Recharts ScatterChart](https://recharts.github.io/en-US/api/ScatterChart/).
At the pinned peer version, fixed-size server rendering produces no Scatter SVG marks. Supply server-rendered data or a summary.
### ScatterSeries
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `Row[]` | — | Native point records; axis keys read these records. |
| `seriesKey` | `string` | String `dataKey` | Config and visibility identity; use explicitly for cohorts. |
| `shape` | `"circle" \| "cross" \| "diamond" \| "square" \| "star" \| "triangle" \| "wye"` or native custom shape | `"circle"` | Built-in symbol or consumer-owned renderer. |
| `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Finish for built-in symbols, preserving native geometry. |
| `fill` | `string` | Config color when identified | Explicit color overrides metadata. |
| `hide` | `boolean` | `false` | Native hiding; Root selection also applies. |
| `xAxisId` / `yAxisId` / `zAxisId` | `string \| number` | `0` | Native axis association. |
Native per-series animation is disabled. Kind does not coerce or deduplicate coordinates; Cells, labels, events and custom shapes remain available. See [Recharts Scatter](https://recharts.github.io/en-US/api/Scatter/).
### ScatterTooltip
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `pointLabel` | `(record: Row) => ReactNode` | Config series label | Receives the actual selected native record. |
| `zDimension` | `ScatterSizeDimension` | — | Explicit raw zero/missing size recovery; no geometry change. |
| `missingValue` | `ReactNode` | `"No data"` | Text for a recovered missing measurement. |
| `axisId` | `string \| number` | `0` | Match a custom X axis ID for native keyboard navigation. |
| `maxWidth` | `number` | `180` | Kind tooltip width bound in pixels. |
| `content` | `ReactElement \| ((props: TooltipRenderProps) => ReactNode)` | Kind Scatter content | Custom content owns dimension rendering. |
`zDimension` accepts a string key or numeric/missing-value accessor, plus optional `name` and `unit`. Native formatters take precedence. See [Recharts Tooltip](https://recharts.github.io/en-US/api/Tooltip/).
Preserve accessible data for every series, including overlapping observations and missing measurements.
### ScatterTooltipContent
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `tooltip` | `TooltipRenderProps` | Required | Native tooltip content callback payload. |
| `pointLabel` / `zDimension` / `missingValue` | Same as `ScatterTooltip` | Same | Compose Kind dimension content inside a custom native adapter. |
| `valueAnimation` | `"shuffle"` | — | Optional numeric readout motion. |
## Shared components
[Root](/charts/docs/chart-components/root/), [SeriesConfig](/charts/docs/chart-components/series-config/), [Axes and grid](/charts/docs/chart-components/axes-grid/), [Legend](/charts/docs/chart-components/legend/), [ResponsiveContainer](/charts/docs/chart-components/responsive-container/).
---
# Waterfall Chart
Trace signed contributions from an opening balance to a closing total.
## Complete Waterfall Chart consumer
Cash rises from $120k to $180k before costs and tax leave $95k.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { CashFlowChart } from "./examples/waterfall/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/waterfall/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
// Values are USD thousands. Checkpoints are absolute; deltas are signed changes.
const entries = [
{ id: "opening", label: "Opening", kind: "start", value: 120 },
{ id: "sales", label: "Sales", kind: "delta", value: 75 },
{ id: "refunds", label: "Refunds", kind: "delta", value: -15 },
{ id: "operating", label: "Subtotal", kind: "subtotal" },
{ id: "costs", label: "Costs", kind: "delta", value: -65 },
{ id: "tax", label: "Tax", kind: "delta", value: -20 },
{ id: "closing", label: "Closing", kind: "end", value: 95 },
] satisfies Chart.WaterfallEntry[];
const data = Chart.computeWaterfallData(entries);
const labels = new Map(data.map((row) => [row.id, row.label]));
const money = (value: number | null) => (value === null ? "Unknown" : `$${value}k`);
const config = {
range: { label: "Cash balance", color: "#733bff" },
} satisfies Chart.SeriesConfig;
export function CashFlowChart() {
return (
●
{" "}
Checkpoint
●
{" "}
Increase
●
{" "}
Decrease
money(Number(value))}
tickMargin={10}
height={36}
/>
labels.get(String(id)) ?? String(id)}
tickMargin={8}
/>
{data.map((row) => (
))}
{
const row = data.find((row) => row.id === String(tooltip.label));
if (!tooltip.active || !row) return null;
return (
{row.label}
{row.kind === "delta"
? "Change"
: row.kind === "subtotal"
? "Subtotal"
: "Checkpoint"}
: {row.kind === "delta" && row.value !== null && row.value > 0 ? "+" : ""}
{money(row.value)}
From: {money(row.start)}
Balance: {money(row.balance)}
);
}}
/>
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/waterfall/README.md)
- [LICENSE](/charts/docs/examples/waterfall/LICENSE)
## Usage
Use `computeWaterfallData` to prepare rows for the bars and connectors.
```tsx title="Composition"
{data.map((row) => )}
{
const row = data.find((row) => row.id === String(tooltip.label));
return tooltip.active && row ? {row.label}: {row.value ?? "Unknown"} ($k); balance {row.balance ?? "Unknown"} ($k)
: null;
}} />
```
Use one unstacked series. `layout="vertical"` produces horizontal bars. Label the category axis from `id`; the color key describes step roles rather than toggling contributions.
## Inputs and checkpoints
`computeWaterfallData(entries, initialBalance = 0)` returns new ordered rows. These values are USD thousands.
| Kind | Supplied value | Behavior |
| --- | --- | --- |
| `"start"` | `number \| null` | Draw zero to value; establish an absolute opening balance. |
| `"delta"` | `number \| null` | Draw previous balance to previous balance plus the signed change. |
| `"subtotal"` | None | Draw zero to current balance without adding it again or resetting it. |
| `"total"` | `number \| null` | Establish an explicit absolute checkpoint. |
| `"end"` | `number \| null` | Establish an explicit closing checkpoint; never infer a final sum. |
Opening $120k, sales and refunds produce a $180k subtotal; costs and tax leave $95k. Checkpoints supply explicit balances and may intentionally differ from the preceding result.
Connectors join adjacent known, matching balances. Place them before bars; subset chart and connector rows together. Missing geometry or disagreeing checkpoints break the connection.
## Missing and zero values
A null change/checkpoint makes the balance unknown until a known checkpoint restores it. To start unknown, call `computeWaterfallData(entries, null)`.
## Complete Missing and zero values consumer
An unknown change breaks the chain; an audited total restores it, followed by a true zero change.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { MissingCashFlowChart } from "./examples/waterfall-missing/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/waterfall-missing/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
// Values are USD thousands. Checkpoints are absolute; deltas are signed changes.
const entries = [
{ id: "opening", label: "Opening", kind: "start", value: 80 },
{ id: "sales", label: "Sales", kind: "delta", value: 40 },
{ id: "pending", label: "Pending", kind: "delta", value: null },
{ id: "fees", label: "Fees", kind: "delta", value: -10 },
{ id: "audited", label: "Audited", kind: "total", value: 105 },
{ id: "adjustment", label: "No change", kind: "delta", value: 0 },
{ id: "closing", label: "Closing", kind: "end", value: 105 },
] satisfies Chart.WaterfallEntry[];
const data = Chart.computeWaterfallData(entries);
const labels = new Map(data.map((row) => [row.id, row.label]));
const money = (value: number | null) => (value === null ? "Unknown" : `$${value}k`);
const config = {
range: { label: "Cash balance", color: "#733bff" },
} satisfies Chart.SeriesConfig;
export function MissingCashFlowChart() {
return (
●
{" "}
Checkpoint
●
{" "}
Increase
●
{" "}
Decrease
money(Number(value))}
tickMargin={10}
height={36}
/>
labels.get(String(id)) ?? String(id)}
tickMargin={8}
/>
{data.map((row) => (
))}
{
const row = data.find((row) => row.id === String(tooltip.label));
if (!tooltip.active || !row) return null;
return (
{row.label}
{row.kind === "delta"
? "Change"
: row.kind === "subtotal"
? "Subtotal"
: "Checkpoint"}
: {row.kind === "delta" && row.value !== null && row.value > 0 ? "+" : ""}
{money(row.value)}
From: {money(row.start)}
Balance: {money(row.balance)}
);
}}
/>
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/waterfall-missing/README.md)
- [LICENSE](/charts/docs/examples/waterfall-missing/LICENSE)
Pending leaves Fees’ −$10k value known but its geometry unknown; Audited restores $105k. Zero changes retain zero-width geometry, with no fabricated minimum bar.
Values must be finite or null; subtotals must omit `value`. Invalid kinds/IDs, duplicate IDs, omitted change/checkpoint values, nonfinite values and arithmetic overflow throw.
## Materials
Choose paper, plain, clay or glow. Custom `shape` or `filter` owns the finish; arithmetic and connectors stay unchanged.
## Complete Materials consumer
Paper, plain, clay and glow on the same floating rectangles.
Install dependencies with npm install.
### package.json
```json
{
"name": "kind-ui-chart-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@kind-ui/charts": "^0.1.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"recharts": "3.10.1",
"motion": "13.4.6"
},
"devDependencies": {
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"typescript": "5.9.3",
"vite": "8.3.1"
}
}
```
### index.html
```text
Kind UI chart example
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true
},
"include": [
"src"
]
}
```
### src/main.tsx
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { MaterialCashFlowChart } from "./examples/waterfall-materials/example";
import "@kind-ui/charts/styles.css";
import "./example.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing mount element");
createRoot(root).render();
```
### src/example.css
```css
.chart-example {
--foreground: #25252b;
--background: #fff;
--card: #fff;
--card-foreground: #25252b;
--border: #dfe2e8;
--muted: #f3f4f7;
--muted-foreground: #59616f;
--chart-1: #733bff;
--chart-2: #119548;
--chart-3: #d97706;
--chart-4: #f22e79;
color: var(--foreground);
background: var(--background);
font:
1rem / 1.6 system-ui,
sans-serif;
padding: 1rem;
min-width: 0;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.dark .chart-example {
--foreground: #e9e9f0;
--background: #1c1c24;
--card: #252530;
--card-foreground: #e9e9f0;
--border: #454553;
--muted: #30303c;
--muted-foreground: #b2b2c0;
}
.chart-help {
font-size: 0.875rem;
color: var(--muted-foreground);
line-height: 1.6;
margin: 0.7rem 0;
}
.chart-example svg {
max-width: 100%;
}
.chart-example .recharts-text {
fill: var(--muted-foreground);
font-size: 0.875rem;
}
.chart-example [data-kind-ui="chart-legend"] {
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0.7rem 0;
}
.chart-example [data-kind-ui="chart-legend-item"] {
padding: 0;
margin: 0;
}
.chart-example [data-kind-ui="chart-legend-item"]::before {
display: none;
}
.chart-example [data-kind-ui="chart-legend-item"]::marker {
content: "";
}
.chart-example table:not([data-kind-ui^="heatmap-"]) {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
text-align: left;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) th,
.chart-example table:not([data-kind-ui^="heatmap-"]) td {
border-bottom: 1px solid var(--border);
padding: 0.5rem 0.6rem;
white-space: nowrap;
}
.chart-example table:not([data-kind-ui^="heatmap-"]) caption {
text-align: left;
margin: 0.5rem 0;
}
.chart-example details {
margin-top: 0.8rem;
}
.chart-example summary {
cursor: pointer;
font-size: 0.875rem;
}
.table-scroll {
overflow-x: auto;
max-width: 100%;
}
.chart-example :focus-visible {
outline: 2px solid #733bff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.chart-example *,
.chart-example *::before,
.chart-example *::after {
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component animations. */
animation-duration: 0.01ms !important;
/* biome-ignore lint/complexity/noImportantStyles: Honor reduced motion over component transitions. */
transition-duration: 0.01ms !important;
}
}
.chart-example > h2 {
font-size: 0.875rem;
font-weight: 500;
margin: 0 0 1.5rem;
}
.chart-example details {
margin-top: 1.5rem;
}
.chart-example .recharts-text {
font-size: 0.75rem;
}
.chart-example [data-kind-ui="chart-legend"] {
justify-content: center;
gap: 1rem;
margin: 1rem 0 0;
}
.chart-example [data-kind-ui="chart-legend"] button {
font-size: 0.75rem;
}
```
### src/examples/waterfall-materials/example.tsx
```tsx
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
// Values are USD thousands. Checkpoints are absolute; deltas are signed changes.
const entries = [
{ id: "opening", label: "Opening", kind: "start", value: 120 },
{ id: "sales", label: "Sales", kind: "delta", value: 75 },
{ id: "refunds", label: "Refunds", kind: "delta", value: -15 },
{ id: "operating", label: "Subtotal", kind: "subtotal" },
{ id: "costs", label: "Costs", kind: "delta", value: -65 },
{ id: "tax", label: "Tax", kind: "delta", value: -20 },
{ id: "closing", label: "Closing", kind: "end", value: 95 },
] satisfies Chart.WaterfallEntry[];
const data = Chart.computeWaterfallData(entries);
const labels = new Map(data.map((row) => [row.id, row.label]));
const money = (value: number | null) => (value === null ? "Unknown" : `$${value}k`);
const config = {
range: { label: "Cash balance", color: "#733bff" },
} satisfies Chart.SeriesConfig;
export function MaterialCashFlowChart({ material = "paper" }: { material?: Chart.BarMaterial }) {
return (
●
{" "}
Checkpoint
●
{" "}
Increase
●
{" "}
Decrease
money(Number(value))}
tickMargin={10}
height={36}
/>
labels.get(String(id)) ?? String(id)}
tickMargin={8}
/>
{data.map((row) => (
))}
{
const row = data.find((row) => row.id === String(tooltip.label));
if (!tooltip.active || !row) return null;
return (
{row.label}
{row.kind === "delta"
? "Change"
: row.kind === "subtotal"
? "Subtotal"
: "Checkpoint"}
: {row.kind === "delta" && row.value !== null && row.value > 0 ? "+" : ""}
{money(row.value)}
From: {money(row.start)}
Balance: {money(row.balance)}
);
}}
/>
);
}
```
Complete setup files:
- [README.md](/charts/docs/examples/waterfall-materials/README.md)
- [LICENSE](/charts/docs/examples/waterfall-materials/LICENSE)
## Motion and inspection
`WaterfallChart` aliases `BarChart`. Entrance reveals ranges from numeric zero, not sequential arithmetic. Interaction finishes it; reduced motion skips it. Native per-series animation stays disabled.
Use `value`, `start` and `balance` in tooltip content; native range payloads contain endpoints. `filterNull={false}` permits unknown-step inspection. Range bars cannot use scalar category emphasis.
## API reference
Focused props for ordered data and explicit Recharts composition; native axes, Cells and reference lines retain their geometry.
### computeWaterfallData
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `entries` | `readonly WaterfallEntry[]` | Required | Ordered inputs with unique nonempty `id`, `label`, `kind` and kind-specific value. |
| `initialBalance` | `number \| null` | `0` | Balance before the first entry; `null` means unknown. |
Returns `WaterfallDatum[]` with IDs, labels, kinds, original/computed values, endpoints and balances. `range` sorts endpoints or becomes null; use `value` for direction because sorting loses sign.
### WaterfallChart
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `WaterfallDatum[]` | — | Computed or host-owned range rows. |
| `layout` | `"horizontal" \| "vertical"` | `"horizontal"` | Vertical bars or horizontal bars, respectively. |
| `animate` | `boolean \| BarAnimation` | `false` | Chart-owned motion with reduced-motion handling. |
| `barCategoryGap` | `number \| string` | `"10%"` | Native category spacing; these components use `"32%"`. |
| `accessibilityLayer` | `boolean` | `true` in Recharts | Keyboard category inspection; set explicitly here. |
| `aria-label` / `aria-labelledby` | `string` | — | Accessible plot name, including units. |
`BarAnimation` shares `LineAnimation`: default duration `1000`, easing `[0.25, 0.1, 0.25, 1]`, and supported inspection `hoverTransition`. See [Recharts BarChart](https://recharts.github.io/en-US/api/BarChart/).
### WaterfallSeries
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `seriesKey` | `string` | `"range"` | Metadata and controlled visibility identity. |
| `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Finish on native rectangles; custom shapes/filters retain ownership. |
| `hide` | `boolean` | `false` | Hide all bars; pass the same value to connectors. |
| `radius` | `number \| [number, number, number, number]` | `0` | Native rectangle corner radius. |
| `children` | `ReactNode` | — | Native `Cell` and `LabelList` composition. |
Binds `range` with zero minimum size; rejects `data`, `dataKey`, `stackId` and `minPointSize`. Native shapes/events remain available. See [Recharts Bar](https://recharts.github.io/en-US/api/Bar/) and [Cell](https://recharts.github.io/en-US/api/Cell/).
### WaterfallConnectors
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `readonly WaterfallDatum[]` | Required | The same ordered rows supplied to the chart. |
| `seriesKey` | `string` | `"range"` | Match the bars' visibility identity. |
| `hide` | `boolean` | `false` | Match native bar hide state. |
| `position` | `"start" \| "middle" \| "end"` | `"middle"` in Recharts | Category-band alignment; set `"middle"` explicitly here. |
| `xAxisId` / `yAxisId` | `string \| number` | `0` | Match the series' axes. |
| `strokeDasharray` | `string` | `"3 3"` | Connector dash pattern. |
| `zIndex` | `number` | `100` | Native layer order; endpoints sit under bars. |
Native `ReferenceLine` segments without pointer capture; `x`, `y`, `segment` and `children` are excluded. See [Recharts ReferenceLine](https://recharts.github.io/en-US/api/ReferenceLine/).
## Shared components
[Root](/charts/docs/chart-components/root/), [SeriesConfig](/charts/docs/chart-components/series-config/), [Tooltip](/charts/docs/chart-components/tooltip/), [Axes and grid](/charts/docs/chart-components/axes-grid/).
---
# Composition
Keep Line context, plot parts and application data explicit.
## Ownership
| Responsibility | Owner |
| --- | --- |
| Data, units, domains and application state | Application |
| Native plot geometry and axis semantics | Recharts |
| Chart context, configured defaults, responsive sizing, series, legend and tooltip presentation | Kind UI |
Configured `LineChart` owns its `Root`. Pass `data` and `config`, then compose the exported grid, axes, `LineSeries` and `Tooltip` as children. `legend={{}}` enables the internally positioned legend. Do not add another `Root` around configured `LineChart`.
Omit explicit children and pass `xDataKey` to use generated parts. Without `config`, the legacy `Root` + `LineChart` composition remains available. See the [Line API](/charts/docs/components/line/#api-reference) for these two modes and their defaults.
## Dimensions and tokens
Give the chart measurable space. Configured `LineChart` defaults to full width and 280px height, with dimensions overridable by props and styles. The package stylesheet uses scoped config colors and application tokens such as `--foreground`, `--card`, `--border` and `--muted-foreground`; the complete consumer includes a stylesheet defining them.
## Motion and access
Configured Line defaults to maintained motion and respects reduced-motion preferences; `animate={false}` disables it. Provide an accessible name, meaningful axes/units and a [complete data alternative](/charts/docs/guides/accessibility/). Custom shapes, filters and renderers retain their own behavior and styling. Materials decorate lines without changing data or geometry.
---
# Identity and colors
Separate measured values, stable identities and presentation metadata.
| Name | Job | Example |
| --- | --- | --- |
| `dataKey` | Read a measured field or accessor | `"visitors"` reads each row's value |
| `seriesKey` | Identify series metadata and visibility separately | `"visitors"` identifies an accessor series |
| Category identity | Identify one row/category independently of its value | `"search"` identifies a slice whose `value` is 35 |
| `config` | Label, color, formatter and optional icon | `visitors: { label: "Visitors", color: "#733bff" }` |
## Series
A string series `dataKey` normally identifies its config entry. Function and numeric keys need `seriesKey` for controlled visibility. Configured Line's `series` entries always supply unique keys in config. An accessor can change how a value is read without changing its identity:
```tsx title="AccessorChart.tsx"
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [{ month: "Jan", count: 42 }, { month: "Feb", count: 58 }];
const config = { visitors: { label: "Visitors", color: "#733bff" } };
export function AccessorChart() {
return row.count, seriesKey: "visitors" }]}
animate={false} aria-label="Monthly visitors" />;
}
```
Series metadata supplies default paint; explicit native paint and custom renderers retain control. Configured Line toggles series automatically. `defaultVisibleSeries` sets the initial list; controlled visibility needs `visibleSeries` and `onVisibleSeriesChange`. Without the callback, the list is read-only. Keep the visibility mode stable or remount to change it.
## Category and node colors
Try the short [identity and layout recipes](/charts/docs/guides/identity-layout/) or the complete Components examples.
- [Pie/Donut and Radial category defaults](/charts/docs/guides/identity-layout/): `PieSeries.categoryKey` reads explicit series rows; `RadialBarChart.categoryKey` reads chart rows. A row such as `{ id: "search", value: 35 }` uses `dataKey="value"` for geometry and `categoryKey="id"` for config color. IDs must resolve to string keys in `Root.config`; use an accessor for nested fields. Reorder/filter keeps color tied to identity. Legend order follows config order; category filtering stays application-owned.
- [Sankey metadata](/charts/docs/components/sankey/): `SankeyChart.nodeConfig` maps node IDs to labels/colors. Default nodes use their own color; solid links use the source color. Composed gradient links resolve both endpoints. `SankeyLegend` receives the same config explicitly and remains a static list.
- [ActivityRings](/charts/docs/components/radial/#activityrings): ring `key` identifies config; `value` and optional `domain` determine progress. The helper supplies radial composition and preserves original values for its default tooltip. Explicit Radial composition remains available.
Category defaults fill missing native paint. Explicit series fill disables them; datum/Cell fill takes precedence. Existing Cell props/handlers and custom shapes remain authoritative. Overrides can intentionally differ from legend metadata; update config too when the legend should match. No positional palette or universal identity schema is implied.
Heatmap uses row/column identity and a quantitative scale, rather than series colors. Its [compact layout](/charts/docs/components/heatmap/#compact-activity-grid) adds `HeatmapGrid.layout` for cell size, gap and visual headers; accessible headers and keyboard navigation remain present.
## Inspection and emphasis
Tooltip inspection and paint emphasis are separate contracts. Hiding series does not rewrite the dataset. Automatic emphasis is family-specific; custom renderers keep their own behavior. See the relevant Components page for supported inspection, legend and emphasis options.
---
# Accessibility and motion
Name the Line chart and provide complete data access beyond inspection.
Give each chart a meaningful accessible name, describe its units and comparison, and expose the records through a table, list or summary. Native SVG inspection and a tooltip do not replace complete data access. Hosts own loading/error/empty states and filtering meaning. These docs include a screen-reader data table for each Line example.
## Native inspection limits
| Composition | Access and limitation |
| --- | --- |
| Configured Line | Provide `aria-label` or `aria-labelledby`; native category keyboard inspection has chart instructions |
| Explicit Line parts | Keep axis labels and units meaningful; a complete data alternative retains missing, zero and hidden-series records |
Custom tooltip content owns its announcements. Config icons are decorative and do not replace labels or units. Test naming and formatting with the intended audience; Chromium automation does not establish manual screen-reader conformance. Other browsers and assistive technologies remain unverified.
## Motion is an explicit choice
Configured `LineChart` defaults to maintained animation; use `animate={false}` for a still view. Maintained entrances respect live `prefers-reduced-motion` and settle on final geometry when interaction or relevant data/layout/visibility changes interrupt an entrance. Custom renderers own their animation and reduced-motion behavior. Materials are static.
Test narrow containers, independent charts, keyboard focus, Escape, missing and zero values with the copied example. Keep a host summary/table available before client chart geometry mounts; check SSR geometry and hydration in your framework integration.
---
# Identity and layout examples
Compose category colors, compact matrices and activity rings.
Use category colors, compact Heatmap layout and ActivityRings through public exports. For complete examples and props, see [Pie](/charts/docs/components/pie/), [Heatmap](/charts/docs/components/heatmap/), [Radial](/charts/docs/components/radial/) and [Sankey](/charts/docs/components/sankey/).
## Category colors
Use a stable row ID to resolve Pie/Donut colors from config. The same config supplies legend metadata. Values still control slice geometry; reordering rows does not reassign colors.
```tsx title="CategoryDonut.tsx"
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [{ id: "search", value: 35 }, { id: "social", value: 65 }];
const config = {
search: { label: "Search", color: "#733bff" },
social: { label: "Social", color: "#327448" },
};
export function CategoryDonut() {
return
item.payload.id} />
;
}
```
`itemKey` aligns tooltip metadata with row identity. For composed Radial categories, `categoryKey` belongs on `RadialBarChart`, whose rows feed the series. Explicit series/datum/Cell paint and custom renderers keep control. See [identity and colors](/charts/docs/concepts/identity/) for precedence and Sankey's distinct node metadata map.
## Compact activity grid
`HeatmapGrid.layout` supplies square cell dimensions, spacing and visual header controls. Hidden headers keep programmatic associations. A width-constrained host retains horizontal scrolling and keyboard access.
```tsx title="CompactActivity.tsx"
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const rows = ["Mon", "Tue"];
const columns = ["Week 1", "Week 2"];
const data = rows.flatMap((row, r) => columns.map((column, c) =>
({ row, column, value: r + c })));
const scale = Chart.createHeatmapScale({ domain: [0, 2], colors: ["#eef4eb", "#327448"] });
export function CompactActivity() {
return
} />
;
}
```
Numbers use pixels; valid CSS lengths are also supported. Cell size must be positive and gap nonnegative. Omitted layout retains fluid cells, 3px spacing and visible labels. Custom cell content/styles can enlarge cells; native table composition remains available. The packed feature checks cover keyboard, narrow-width scrolling and programmatic accessibility; manual screen-reader listening remains untested.
## Activity rings
`ActivityRings` supplies context, axes, tracks, tooltip and static legend. Ring keys identify config; domains normalize geometry while the default tooltip retains original values. Do not wrap it in another `Root`.
```tsx title="DailyActivity.tsx"
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const config = {
move: { label: "Move", color: "#e12465" },
exercise: { label: "Exercise", color: "#327448" },
stand: { label: "Stand", color: "#733bff" },
};
const rings = [
{ key: "move", value: 350, domain: [0, 500] as const },
{ key: "exercise", value: 30, domain: [0, 60] as const },
{ key: "stand", value: 9, domain: [0, 12] as const },
];
export function DailyActivity() {
return ;
}
```
Defaults are a responsive 300px chart, clockwise full sweep, tracks, rounded ends, static legend and motion off. Array order runs from inner to outer. Native geometry, series paint/shape, labels and per-ring Cell props remain overridable. Custom tooltip payload `value` is normalized progress; `rawValue` is the original value. Use explicit [Radial composition](/charts/docs/components/radial/) for full axes, data and child control. [Inter-chart choreography](/charts/docs/guides/motion/#custom-choreography) stays application-owned.
---
# Materials
Choose a Line finish while preserving its quantitative reading.
The Line material vocabulary is `plain`, `paper`, `clay` and `glow`. The finishes are static decoration, independent of curve and chart motion. Glass is paused and unmerged; it is not a supported material value.
Paper supplies grain and a pencil treatment, Clay supplies soft matte relief, and Glow supplies decorative light. A material does not transform data or improve keyboard coverage.
| Public prop | Boundary |
| --- | --- |
| Configured `LineChart.material` | Applies to generated series |
| Explicit `LineSeries.material` | Applies to its maintained curve; native dots and labels retain ownership |
## Consumer paint takes precedence
Custom shapes and explicit filters retain their own finish. Native gradients, clipping, stroke, opacity and handlers remain under their documented contracts. Read the installed declarations and [Line API](/charts/docs/components/line/#lineseries) before relying on a custom renderer.
## Choose a finish with representative data
Inspect thin lines, dense overlaps, chart edges, missing values, zero and dark mode. Plot clipping can trim exterior effects. Filters add rendering work; plain lines provide a comparison. Keep labels and numeric boundaries legible. Automated coverage targets Chromium and pinned peers; other engines and print/export renderers are not established by that evidence.
---
# Motion
Choose family entrances, inspection motion or application-owned timing.
`animate={false}` disables chart entrances and inspection movement. Opted-in tooltip digit shuffle is independent; omit `valueAnimation="shuffle"` too when the application owns all timing. `true` enables family defaults; supported object forms tune them. Reduced-motion preferences show final geometry and disable Kind inspection motion. This does not remove the required Motion peer or change package contents. Custom renderers and application animations retain their own responsibilities.
## Family defaults
The matrix describes the public components. “Update” means data/geometry changes, separately from tooltip movement or visibility fades. No family promises automatic interpolation between datasets or chart types. Changed inputs or interaction finish an entrance; remount when an intentional replay is needed.
| Family / mode | Default | `animate` shape and entrance defaults | Entrance / update / hover |
| --- | --- | --- | --- |
| [Configured Line](/charts/docs/components/line/) with generated or explicit children | `true` | Boolean or `LineAnimation`; 1000ms, cubic easing | Plot-width reveal; data updates settle geometry, visibility changes fade lines; default active dot and Kind tooltip move. |
| [Explicit Line](/charts/docs/components/line/) under `Root` | `false` | Boolean or `LineAnimation`; same options | Same motion when enabled; application owns visibility. |
| [Area](/charts/docs/components/area/) | `false` | Boolean or `AreaAnimation`; 1000ms, cubic easing | Plot-width reveal; updates finish entrance; Kind tooltip moves. |
| [Bar](/charts/docs/components/bar/) | `false` | Boolean or `BarAnimation`; 1000ms, cubic easing | Reveal from zero clamped to numeric domain; updates finish entrance; Kind tooltip moves. Category emphasis is separately opt-in. |
| [Histogram](/charts/docs/components/histogram/) | `false` | Boolean or `BarAnimation`; same options | Bar entrance; bin updates finish it; Kind tooltip moves. |
| [Waterfall](/charts/docs/components/waterfall/) | `false` | Boolean or `BarAnimation`; same options | Bar range entrance, not sequential arithmetic; updates finish it; Kind tooltip moves. |
| [Box Plot](/charts/docs/components/box-plot/) | `false` | Boolean or `BarAnimation`; same options | Clipped entrance of composed marks; updates finish it; Kind tooltip moves. |
| [Combo](/charts/docs/components/combo/) | `false` | Boolean or `ComboAnimation`; shared 1000ms/cubic defaults, per-family reveal overrides | Line/Area width and Bar zero-based reveals; updates finish entrances, Line visibility fades; shared Kind tooltip/default Line active-dot motion. |
| [Pie / Donut](/charts/docs/components/pie/) | `false` | Boolean or `PieAnimation`; 1000ms, `"easeOut"` | Angular sector reveal; updates finish it; Kind tooltip moves and eligible categories receive emphasis. |
| [Radar](/charts/docs/components/radar/) | `false` | Boolean or `RadarAnimation`; 1000ms, `"easeOut"` | Default polygon reveals from center; updates finish it; Kind tooltip moves, spoke inspection is separate. |
| [Radial Bar](/charts/docs/components/radial/) | `false` | Boolean or `RadialBarAnimation`; 1000ms, `"easeOut"` | Directional sector reveal; updates finish it; Kind tooltip moves. |
| [Scatter](/charts/docs/components/scatter/) | `false` | Boolean or `ScatterAnimation`; 700ms, `"easeOut"` | In-place mark fade; updates finish it; Kind tooltip moves, custom active shapes remain native. |
| [Sankey](/charts/docs/components/sankey/) | `false` | Boolean or `SankeyAnimation` (`revealDurationMs` only); 450ms | Supported Kind link paint flows along fixed paths; updates/resize finish it; custom renderers own hover motion. |
| [Heatmap](/charts/docs/components/heatmap/) | `false` | Boolean only; built-in diagonal fade | Cells fade without moving; inputs/resize finish it; focus and tooltip inspection do not imply chart hover tweening. |
[ActivityRings](/charts/docs/components/radial/#activity-rings) is available from the public package, inherits Radial entrance options and defaults to `false`.
## Option objects
Line, Area, Bar, Pie, Radar, Radial and Scatter export family aliases with `revealDurationMs`, `revealEasing` and `hoverTransition`. Entrance duration is milliseconds; Motion transition duration is seconds. The default hover transition is a spring with stiffness 210, damping 28 and mass 0.8. Cartesian reveal easing defaults to `[0.25, 0.1, 0.25, 1]`; polar and Scatter default to `"easeOut"`.
```tsx title="MotionOptions.tsx"
import * as Chart from "@kind-ui/charts";
export const line = { revealDurationMs: 600, revealEasing: "easeOut",
hoverTransition: { type: "tween", duration: 0.12 } } satisfies Chart.LineAnimation;
export const area = line satisfies Chart.AreaAnimation;
export const bar = line satisfies Chart.BarAnimation;
export const pie = line satisfies Chart.PieAnimation;
export const radar = line satisfies Chart.RadarAnimation;
export const radial = line satisfies Chart.RadialBarAnimation;
export const scatter = line satisfies Chart.ScatterAnimation;
export const combo = { ...line, lineReveal: false,
areaReveal: { revealDurationMs: 500 }, barReveal: { revealDurationMs: 700 }
} satisfies Chart.ComboAnimation;
export const sankey = { revealDurationMs: 900 } satisfies Chart.SankeyAnimation;
export const heatmap: NonNullable = true;
```
Pass the matching object to its chart's `animate` prop. Histogram, Waterfall and Box Plot use `BarAnimation`. Combo family overrides tune entrances only; `false` skips that family's reveal while shared inspection motion remains available. Pie and Radial additionally expose `animationDirection="clockwise"` or `"anticlockwise"`; this changes entrance direction, preserving final native geometry.
Native per-series animation is disabled by Kind series so the chart owns timing. Shape/filter/transform escape hatches can bypass a built-in reveal; refer to each Components page for its exact boundary. Tooltip digit shuffle is separately opt-in through `valueAnimation="shuffle"`, respects reduced motion and is not a data/geometry transition. Custom tooltip content owns its own animation.
## Custom choreography
A Line-to-Bar transformation, scene sequence or video timeline is application-authored. This example uses a consumer-supplied progress value to crossfade two charts. It does not morph their geometry. Both charts disable built-in timing; the caller owns the timeline and should supply final progress for reduced motion.
```tsx title="ChartTransition.tsx"
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [{ month: "Jan", visitors: 42 }, { month: "Feb", visitors: 58 }];
const config = { visitors: { label: "Visitors", color: "#733bff" } };
export function ChartTransition({ progress }: { progress: number }) {
const t = Math.max(0, Math.min(1, progress));
return
= 0.5} inert={t >= 0.5}
style={{ gridArea: "1 / 1", opacity: 1 - t }}>
;
}
```
Keep an accessible [data alternative](/charts/docs/guides/accessibility/) outside a decorative video scene. External timing and custom renderers remain consumer-owned even when chart motion is disabled.
## Custom choreography
`ActivityRings` uses RadialBar entrance options with motion off by default. Coordinate motion across charts and custom renderers in your application.
---
# Package integration
Keep public imports, styling and consumer verification aligned.
Install charts using [installation](/charts/docs/start/installation/). Import components
and types from `@kind-ui/charts` and the stylesheet once at the application entry.
Use [AI agents](/charts/docs/agents/consumer/) to retrieve complete examples and API tables.
When updating a dependency, review the [changelog](https://github.com/bhaveshchow20/kind-ui/blob/main/packages/charts/CHANGELOG.md)
and rebuild your consumer. Check strict types, chart sizing, keyboard navigation,
controlled visibility, zero/missing values and reduced motion. Keep host-owned
data alternatives complete as observations change.
---
# Troubleshooting
Resolve Line setup and composition issues through public package APIs.
| Symptom | Check |
| --- | --- |
| Registry cannot find the package | Check the package name, registry configuration and network access; follow [installation](/charts/docs/start/installation/) |
| Composition import unavailable | Use documented public exports and check the installed package types for configured Line props |
| Motion entry cannot resolve | Motion is a required peer; `@kind-ui/charts/motion` is not exported |
| Chart has no size | Give its responsive parent measurable space; check configured width/height and styles |
| Configured chart reports a nested Root | Remove the outer Root; configured `LineChart` owns it |
| Explicit composition has no legend | Set `legend={{}}` on configured `LineChart` |
| Visibility does not update | Pair controlled `visibleSeries` with `onVisibleSeriesChange` |
| Function-key series ignores visibility | Supply its config identity through `seriesKey` |
| Arrows stay on a hovered category | Native inspection prioritizes the pointer; move it out and enter with the keyboard |
| Material does not affect a custom mark | Explicit shapes and filters retain ownership |
| Copied source has an earlier curve or material | Copy again after selecting the desired variant |
Report failures with the installed package and peer versions, complete copied example, commands and expected/observed behavior. For website friction, include the page, viewport and steps. Classify installation/export failures as packaging, unclear instructions as docs, and incorrect behavior of documented composition as a runtime defect.
---
# Installation
Install charts and import the public components and stylesheet.
```sh
npm install @kind-ui/charts
```
npm installs the required peers automatically. The package is ESM. Supported peers
are React/React DOM `^19.3.0`, Recharts `^3.10.1` and Motion `^13.4.6`. Motion is
required even when animation is disabled.
## Imports and CSS
```tsx
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
```
Import the stylesheet once at the application entry. Use public composition parts
such as `CartesianGrid`, `XAxis`, `YAxis`, `ResponsiveContainer` and `LabelList`.
There is no `@kind-ui/charts/motion` entry. Next applications need a client
component boundary when supplying callbacks, refs or state.
Use the complete consumer files on each component page or adapt their chart
composition into your app. Supply chart sizing, an accessible name and a complete
[data alternative](/charts/docs/guides/accessibility/). See [composition](/charts/docs/concepts/composition/)
for styling and [AI agents](/charts/docs/agents/consumer/) for example retrieval and verification.
---
# Quickstart
Build a small chart, then add sizing, inspection and composition.
Use the [installation guide](/charts/docs/start/installation/) first. Each example below is a complete component with public imports. Keep the stylesheet at your application entry when combining them.
## 1. Start with a chart
Configured `LineChart` supplies its own context, axes and series. `dataKey` reads a value; config supplies its label and color. Here `visitors` serves both roles. Tooltip, legend and motion are disabled for this first step.
```tsx title="MinimalChart.tsx"
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [{ month: "Jan", visitors: 42 }, { month: "Feb", visitors: 58 }];
const config = { visitors: { label: "Visitors", color: "#733bff" } };
export function MinimalChart() {
return ;
}
```
## 2. Size and color it
Configured Line fills its parent width and defaults to 280px height. Set `height` for your layout; let a shrinking flex/grid host use `minWidth: 0`. A stable config key keeps metadata separate from the measured values. Changing its color changes the series paint; it does not change the data.
```tsx title="SizedChart.tsx"
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [{ month: "Jan", visitors: 42 }, { month: "Feb", visitors: 58 }];
const config = { visitors: { label: "Visitors", color: "#327448" } };
export function SizedChart() {
return
;
}
```
Explicit families use `Root` and `ResponsiveContainer` inside a measurable host; see [composition](/charts/docs/concepts/composition/). Colors follow series metadata, with native paint overrides available. See [identity and colors](/charts/docs/concepts/identity/) for category and Sankey contracts.
## 3. Add inspection and motion
Generated parts include tooltip and legend unless disabled. The legend toggles configured Line series without application wiring. `animate` is on by default here; the object form tunes its entrance. Hover inspection uses a separate transition. Reduced-motion preferences skip Kind motion.
```tsx title="InspectedChart.tsx"
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [{ month: "Jan", visitors: 42 }, { month: "Feb", visitors: 58 }];
const config = { visitors: { label: "Visitors", color: "#327448" } };
export function InspectedChart() {
return ;
}
```
For controlled visibility, pair `visibleSeries` with `onVisibleSeriesChange`. Use `animate={false}` when a video timeline or application owns frame timing. See [motion defaults](/charts/docs/guides/motion/) and the [Line API](/charts/docs/components/line/#api-reference).
## 4. Compose the parts
Explicit children replace generated plot parts. Configured Line still owns its context, sizing and visibility; omit `xDataKey` and compose the axes yourself. Enable its positioned legend with `legend={{}}`.
```tsx title="ComposedChart.tsx"
"use client";
import * as Chart from "@kind-ui/charts";
import "@kind-ui/charts/styles.css";
const data = [{ month: "Jan", visitors: 42 }, { month: "Feb", visitors: 58 }];
const config = { visitors: { label: "Visitors", color: "#327448" } };
export function ComposedChart() {
return
;
}
```
Choose explicit `Root` composition for full native axes, geometry and state ownership. The [Components](/charts/docs/components/area/) show family-specific paths. Data, units, layout, accessible alternatives and [inter-chart choreography](/charts/docs/guides/motion/#custom-choreography) remain application-owned.