# Kind UI charts Composable React charts built on Recharts and Motion. Build charts with reusable components, expressive materials and motion. Keep control of your data, styling and interactions. Start with [installation](/charts/docs/start/installation/) and [Quickstart](/charts/docs/start/quickstart/), then choose a [chart](/charts/docs/components/line/). Each component page includes a live preview, complete code and an API reference. ## Explore - [Identity and colors](/charts/docs/concepts/identity/): labels, colors and stable series keys. - [Materials](/charts/docs/guides/materials/): plain, paper, clay and glow finishes. - [Motion](/charts/docs/guides/motion/): entrances and custom animation. - [Accessibility](/charts/docs/guides/accessibility/): keyboard access and data alternatives. For AI-assisted development, use the [consumer guide](/charts/docs/agents/consumer/) and [llms.txt](/charts/docs/llms.txt). --- # AI agents Build charts from complete examples and documented public APIs. Start with [installation](/charts/docs/start/installation/) and [llms.txt](/charts/docs/llms.txt), then retrieve the relevant component Markdown. [llms-full.txt](/charts/docs/llms-full.txt) includes complete examples and API tables. Copy prompt follows the selected example, curve and material. Copy Markdown provides the page and its default examples. ## Build a chart - Import components from `@kind-ui/charts` and its stylesheet once at the application entry. - Use configured `LineChart` for Root and sizing defaults. Area and Bar use explicit composition. - Keep data, units, domains and visibility state in your application. - Include a chart name and complete data alternative. ## Verify Typecheck and build the consumer. Inspect mobile layout, keyboard access, legend toggles, missing and zero values, contrast and reduced motion. --- # Axes and Grid Set scales, tick labels and guides for Cartesian and polar charts. Kind exports these Recharts components directly. Compose them inside the chart; their props and defaults remain native. ## XAxis and YAxis For category columns and time-series plots, use a category X axis and numeric Y axis. ```tsx `${value}`} /> ``` Use `type="number"` on both axes for [Scatter](/charts/docs/components/scatter/). For horizontal [Bar](/charts/docs/components/bar/) charts, use numeric X and category Y with `layout="vertical"`. `dataKey` selects a field or accessor. `domain` sets numeric bounds, `tickFormatter` formats labels, and `height` (X) or `width` (Y) reserves label space. Match each series' axis IDs when composing multiple scales. See [Recharts XAxis](https://recharts.github.io/en-US/api/XAxis/) and [YAxis](https://recharts.github.io/en-US/api/YAxis/). ## CartesianGrid Use `horizontal` and `vertical` to choose guide directions. `stroke` and `strokeDasharray` style the guides independently of series paint. See [Recharts CartesianGrid](https://recharts.github.io/en-US/api/CartesianGrid/). ## ZAxis For bubble plots, `ZAxis` maps a third measurement to symbol area, not radius. ```tsx ``` Set `domain` for consistent size comparisons. See [Recharts ZAxis](https://recharts.github.io/en-US/api/ZAxis/) and the [Scatter size guidance](/charts/docs/components/scatter/#bubble-size) for zero or missing measurements. ## Polar axes and grid `PolarAngleAxis` labels radar dimensions or maps numeric values onto angles. `PolarRadiusAxis` defines the radial scale or category ring bands. Set explicit bounds for comparable scores and progress values. ```tsx ``` `PolarGrid` supports polygon or circular guides. See [Recharts PolarAngleAxis](https://recharts.github.io/en-US/api/PolarAngleAxis/), [PolarRadiusAxis](https://recharts.github.io/en-US/api/PolarRadiusAxis/) and [PolarGrid](https://recharts.github.io/en-US/api/PolarGrid/). --- # Labels and Cells Add value labels, custom chart text and per-item color. Kind exports Recharts' `LabelList`, `Label` and `Cell` directly. Use them within the chart or series supported by Recharts. ## LabelList Nest `LabelList` inside a series to label its marks. `dataKey` selects the label value; `position`, `offset` and `content` control placement and rendering. ```tsx ``` In radial bars, use `content={}` for measured arc text. Recalculate percentage labels after filtering if they describe the visible total. See [Recharts LabelList](https://recharts.github.io/en-US/api/LabelList/). ## Label Use `Label` for axis titles or custom center content. Its `content` callback lets you draw SVG text using the supplied geometry. Check that custom text fits at narrow widths. See [Recharts Label](https://recharts.github.io/en-US/api/Label/). ## Cell Nest `Cell` inside a supported series to override paint per item. Keep Cells in the same order as the rows passed to that series, including after filtering. ```tsx {data.map((row) => )} ``` Custom paint can override a Kind material's finish; see the chart family's material guidance. See [Recharts Cell](https://recharts.github.io/en-US/api/Cell/) for supported props. --- # Legend Display series labels and let readers toggle their visibility. `Legend` reads labels and markers from [Root](/charts/docs/chart-components/root/). Place it outside the plot's [ResponsiveContainer](/charts/docs/chart-components/responsive-container/). ## Usage ```tsx {/* ResponsiveContainer and plot */} ``` With controlled visibility and a change callback, items are buttons with `aria-pressed` and Enter/Space support. Otherwise they display labels. ## Markers and content Configured icons take priority over `legendShape`; otherwise items use color swatches. `hideIcon` forces swatches. For Scatter, set the same symbol on `legendShape` and `ScatterSeries.shape`. Use the render callback for custom item content. Kind keeps the surrounding button and visibility state, so return noninteractive content. ```tsx {({ label, marker, visible }) => ( {marker}{label}{visible ? "" : " (hidden)"} )} ``` ## Props ### Legend | Prop | Type | Required | | --- | --- | --- | | className | `string \| undefined` | No | | hideIcon | `boolean \| undefined` | No | | emphasis | `"none" \| "series" \| undefined` | No | | children | `((item: { key: string; label: string; visible: boolean; marker: React.ReactNode; }) => React.ReactNode) \| undefined` | No | `hideIcon` defaults to `false`; `emphasis` defaults to `"none"`. Use `emphasis="series"` to request highlighting on hover or focus in supported chart families. Scatter does not automatically dim marks. `Legend` accepts native `ul` props and a ref. This is Kind's legend; [Recharts Legend](https://recharts.github.io/en-US/api/Legend/) has a separate API. --- # ResponsiveContainer Size a composed chart to fit its container. Kind exports Recharts' `ResponsiveContainer` directly. Give the plot a measurable width and height. ## Usage ```tsx ``` Keep [Legend](/charts/docs/chart-components/legend/) outside this container. Configured [LineChart](/charts/docs/components/line/) handles its own responsive sizing; use this component for explicit composition. ## Sizing `width` and `height` accept pixel numbers or percentage strings and default to `"100%"`. A percentage height needs a parent with a defined height. Use `aspect` to derive height from width, or `minHeight` and `minWidth` to set lower bounds. In flex and grid layouts, allow the chart's parent to shrink with `min-width: 0`. Check narrow layouts with long axis labels as well as the plot itself. For resize callbacks and the full prop reference, see [Recharts ResponsiveContainer](https://recharts.github.io/en-US/api/ResponsiveContainer/). --- # Root Share series labels, colors and visibility across a chart. `Root` connects your plot, legend and tooltip to a [series config](/charts/docs/chart-components/series-config/). ## Usage ```tsx import * as Chart from "@kind-ui/charts"; const config = { orders: { label: "Orders", color: "#4477aa" }, } satisfies Chart.SeriesConfig; ``` ## Visibility Pass `visibleSeries` to select which configured series are shown. Add `onVisibleSeriesChange` to make [Legend](/charts/docs/chart-components/legend/) items toggle buttons. The callback requires `visibleSeries`; keep that selection in your own state. ```tsx import { useState } from "react"; const [visibleSeries, setVisibleSeries] = useState(Object.keys(config)); {/* Legend and plot */} ``` ## Props ### Root | Prop | Type | Required | | --- | --- | --- | | config | `Readonly["type"]>; formatValue?: (value: Chart.TooltipValueType) => React.ReactNode; }>>` | Yes | | emphasis | `"auto" \| "none" \| undefined` | No | | visibleSeries | `readonly string[] \| undefined` | No | | onVisibleSeriesChange | `((next: string[]) => void) \| undefined` | No | `emphasis` defaults to `"auto"`. Set `"none"` to disable Kind emphasis within this root; individual chart families define which marks support it. `Root` accepts native `div` props and a ref. Each config key supplies a `--color-{key}` CSS variable on that element. --- # SeriesConfig Define labels, colors and value formatting for chart series. `SeriesConfig` maps stable series or category keys to labels, colors and optional formatting. Pass it to [Root](/charts/docs/chart-components/root/) or directly to a configured [LineChart](/charts/docs/components/line/). ## Usage ```tsx title="config.ts" import type { SeriesConfig } from "@kind-ui/charts"; export const config = { visitors: { label: "Visitors", color: "#733bff", formatValue: (value) => typeof value === "number" ? value.toLocaleString("en-US") : String(value), }, subscribers: { label: "Subscribers", color: "#1c9d8f", }, } satisfies SeriesConfig; ``` Use the same keys in `dataKey` or `seriesKey`. For category charts, match the category identity instead of the measured value. See [Identity and colors](/charts/docs/concepts/identity/) for accessor and category examples. Keys must start with a letter and contain only letters, numbers, underscores or hyphens. `Root` exposes each color as `--color-`; for example, `var(--color-visitors)`. ## Metadata Each config entry has the following fields: ### SeriesMetadata | Prop | Type | Required | | --- | --- | --- | | label | `string` | Yes | | color | `string` | Yes | | icon | `React.ComponentType \| undefined` | No | | legendShape | `NonNullable \| undefined` | No | | formatValue | `((value: Chart.TooltipValueType) => React.ReactNode) \| undefined` | No | - `label` appears in the built-in legend and tooltip. - `color` supplies the default series color and marker color. Explicit series paint takes precedence. - `icon` supplies a decorative React component for the legend and tooltip. - `legendShape` selects a Recharts symbol for the legend. A configured icon takes precedence; use the same shape on `ScatterSeries` when its marks should match. - `formatValue` formats non-null tooltip values and returns a React node. A tooltip or payload formatter takes precedence. The legend follows config insertion order. Config describes presentation; chart data and visibility remain separate. --- # Tooltip Show labels and formatted values for the inspected chart item. `Tooltip` uses [series metadata](/charts/docs/chart-components/series-config/) and stays within the plot bounds. Add it inside the chart, within [Root](/charts/docs/chart-components/root/). ## Usage ```tsx ``` Kind hides entries for deselected series. The default content uses configured labels, colors and value formatters; native tooltip formatters take precedence. Keyboard inspection depends on the chart family. Escape dismisses inspection. ## Custom content Use `TooltipContent` to keep Kind's labels and formatting in a custom content callback. Pass the complete callback payload through its `tooltip` prop. ```tsx ( )} /> ``` `TooltipContent` accepts native `div` props and a ref. Its defaults are `missingValue="No data"`, `indicator="line"`, `hideLabel={false}` and `hideIndicator={false}`. It renders nothing when no visible entry has a value. ## Props ### Tooltip ### Tooltip | Prop | Type | Required | | --- | --- | --- | | content | `ContentType \| undefined` | No | | cursor | `CursorDefinition \| undefined` | No | | filterNull | `boolean \| undefined` | No | | shared | `boolean \| undefined` | No | | frameProps | `Omit, HTMLDivElement>, "children" \| "ref"> \| undefined` | No | | maxWidth | `number \| undefined` | No | | itemKey | `((entry: Chart.TooltipRenderProps["payload"][number]) => string) \| undefined` | No | Kind defaults to `cursor={false}`, `filterNull={false}`, `offset={12}` and `maxWidth={180}`. `frameProps` and `ref` address its measured positioning element. `itemKey` resolves metadata and visibility identity for categories or custom data keys. Native selection and formatting props are available; Kind controls positioning, the portal and tooltip transitions. See [Recharts Tooltip](https://recharts.github.io/en-US/api/Tooltip/) for the native props. ### TooltipContent ### TooltipContent | Prop | Type | Required | | --- | --- | --- | | tooltip | `Chart.TooltipRenderProps` | Yes | | missingValue | `React.ReactNode` | No | | hideLabel | `boolean \| undefined` | No | | hideIndicator | `boolean \| undefined` | No | | indicator | `"dashed" \| "dot" \| "line" \| undefined` | No | | itemKey | `((entry: Chart.TooltipRenderProps["payload"][number]) => string) \| undefined` | No | ## Other chart families Use [ScatterTooltip](/charts/docs/components/scatter/#scattertooltip) for X/Y/Z dimensions and [HeatmapTooltip](/charts/docs/components/heatmap/#heatmaptooltip) for heatmap cells. [Sankey](/charts/docs/components/sankey/) uses `Tooltip` imported directly from `recharts`. Its hover tooltip is native; the Sankey example supplies separate keyboard selection and status text. --- # Area Chart Show change over time with filled areas and stacked series. ## Complete Area Chart consumer A single area 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 { VisitorAreaChart } from "./examples/area/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/area/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 VisitorAreaChart() { return ( ); } ``` Complete setup files: - [README.md](/charts/docs/examples/area/README.md) - [LICENSE](/charts/docs/examples/area/LICENSE) ## Usage Add one `AreaSeries` per measure. Use `stackId` to stack related series. ```tsx title="Composition" ``` Area uses explicit composition, without Line’s configured shortcuts. Place `Legend` outside the plot; pair Root visibility with its callback for toggles. ## Curve types Choose smooth, linear or step-after interpolation. ## Complete Curve types consumer Smooth, linear and step-after areas. 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 { VisitorAreaCurveChart } from "./examples/area-curves/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/area-curves/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 VisitorAreaCurveChart({ curve = "monotone", }: { curve?: "monotone" | "linear" | "stepAfter"; } = {}) { return ( ); } ``` Complete setup files: - [README.md](/charts/docs/examples/area-curves/README.md) - [LICENSE](/charts/docs/examples/area-curves/LICENSE) ## Stacked series Matching `stackId` values stack desktop and mobile visitors. Legend toggles recalculate the visible stack. ## Complete Stacked series consumer Monthly desktop and mobile visitors with consumer-owned legend toggles. 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 { DeviceAreaChart } from "./examples/area-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/area-stacked/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: "Jan", desktop: 310, mobile: 110 }, { period: "Feb", desktop: 380, mobile: 200 }, { period: "Mar", desktop: 340, mobile: 170 }, { period: "Apr", desktop: 460, mobile: 300 }, { period: "May", desktop: 390, mobile: 290 }, { period: "Jun", desktop: 520, mobile: 390 }, { period: "Jul", desktop: 450, mobile: 390 }, { period: "Aug", desktop: 580, mobile: 490 }, { period: "Sep", desktop: 500, mobile: 460 }, { period: "Oct", desktop: 610, mobile: 570 }, { period: "Nov", desktop: 540, mobile: 570 }, { period: "Dec", desktop: 650, mobile: 710 }, ]; const config = { desktop: { label: "Desktop", color: "#733bff" }, mobile: { label: "Mobile", color: "#14a39a" }, } satisfies Chart.SeriesConfig; export function DeviceAreaChart() { const [visibleSeries, setVisibleSeries] = useState(["desktop", "mobile"]); return ( ); } ``` Complete setup files: - [README.md](/charts/docs/examples/area-stacked/README.md) - [LICENSE](/charts/docs/examples/area-stacked/LICENSE) ## Materials Choose plain, paper, clay or glow. ## Complete Materials consumer Plain, paper, clay and glow finishes on native area 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 { MaterialAreaChart } from "./examples/area-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/area-materials/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 MaterialAreaChart({ material = "paper", }: { material?: "plain" | "paper" | "clay" | "glow"; } = {}) { return ( ); } ``` Complete setup files: - [README.md](/charts/docs/examples/area-materials/README.md) - [LICENSE](/charts/docs/examples/area-materials/LICENSE) ## API reference Focused props for the composition above; upstream links cover additional native options. ### AreaChart Compose inside `Root` and a sized `ResponsiveContainer`.
| Prop | Type | Default | Description | | --- | --- | --- | --- | | `data` | `Row[]` | — | Observations for explicit series and axes. | | `animate` | `boolean \| AreaAnimation` | `false` | Chart-owned entrance and inspection motion; respects reduced motion. | | `accessibilityLayer` | `boolean` | `true` in Recharts | Enables arrow-key inspection. Set explicitly in these components. | | `aria-label` / `aria-labelledby` | `string` | — | Accessible plot name. | | `margin` | `{ top?: number; right?: number; bottom?: number; left?: number }` | `5` each side | Space around plot parts. | | `stackOffset` | `"none" \| "expand" \| "wiggle" \| "silhouette" \| "sign" \| "positive"` | `"none"` | Native stack calculation. `"expand"` normalizes stacks; labels and tooltip formatting stay consumer-owned. | | `children` | `ReactNode` | — | Explicit axes, areas, grid and tooltip. |
See [Recharts AreaChart](https://recharts.github.io/en-US/api/AreaChart/). ### AreaSeries String `dataKey` values identify config entries. Use `seriesKey` for numeric keys or accessors when controlling visibility.
| Prop | Type | Default | Description | | --- | --- | --- | --- | | `dataKey` | `string \| number \| ((row: Row) => unknown)` | Required | Value field or accessor. | | `seriesKey` | `string` | String `dataKey` | Metadata and visibility identity. | | `type` | `"basis" \| "basisClosed" \| "basisOpen" \| "bumpX" \| "bumpY" \| "bump" \| "linear" \| "linearClosed" \| "natural" \| "monotoneX" \| "monotoneY" \| "monotone" \| "step" \| "stepBefore" \| "stepAfter"` or custom curve function | `"linear"` | Native curve interpolation. | | `stackId` | `string \| number` | — | Shared identity for stacked series. | | `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Area finish; explicit `shape` or `filter` takes precedence. | | `stroke` / `fill` | `string` | Config color | Override outline or fill separately. | | `fillOpacity` | `number` | `0.6` in Recharts | Fill transparency; shown components set it explicitly. | | `connectNulls` | `boolean` | `false` | Connect missing values instead of leaving gaps. | | `hide` | `boolean` | `false` | Hide a series; Root selection also applies. |
Numbers and two-value ranges retain native geometry. `AreaChart` owns motion; per-series animation is disabled. See [Recharts Area](https://recharts.github.io/en-US/api/Area/) and [material support](/charts/docs/guides/materials/). ## 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/). --- # Bar Chart Compare categories with columns, grouped or stacked series, and horizontal bars. ## Complete Bar Chart consumer Weekly pickup orders across five locations with opt-in category emphasis. 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 { PickupOrdersChart } from "./examples/bar/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/bar/example.tsx ```tsx "use client"; import * as Chart from "@kind-ui/charts"; import "@kind-ui/charts/styles.css"; const data = [ { period: "North", orders: 124 }, { period: "East", orders: 186 }, { period: "Central", orders: 238 }, { period: "West", orders: 162 }, { period: "South", orders: 208 }, ]; const config = { orders: { label: "Orders", color: "#733bff" }, } satisfies Chart.SeriesConfig; export function PickupOrdersChart() { return ( ); } ``` Complete setup files: - [README.md](/charts/docs/examples/bar/README.md) - [LICENSE](/charts/docs/examples/bar/LICENSE) ## Usage Add one `BarSeries` per measure. Use `stackId` for stacks and `layout="vertical"` for horizontal bars. ```tsx title="Composition" ``` Bar uses explicit composition. Place `Legend` outside the plot; pair Root visibility with its callback for toggles. ## Grouped and stacked series Compare grouped loans or stack them with matching `stackId` and axes. Legend toggles recalculate the visible groups or stacks. ## Complete Grouped and stacked series consumer Print and digital library loans, with consumer-owned legend visibility. 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 { LibraryLoansChart } from "./examples/bar-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/bar-comparison/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: "Fiction", print: 184, digital: 116 }, { period: "History", print: 128, digital: 72 }, { period: "Science", print: 156, digital: 104 }, { period: "Arts", print: 112, digital: 88 }, { period: "Travel", print: 96, digital: 64 }, ]; const config = { print: { label: "Print", color: "#733bff" }, digital: { label: "Digital", color: "#14a39a" }, } satisfies Chart.SeriesConfig; export function LibraryLoansChart({ arrangement = "grouped", }: { arrangement?: "grouped" | "stacked"; } = {}) { const [visibleSeries, setVisibleSeries] = useState(["print", "digital"]); const stackId = arrangement === "stacked" ? "loans" : undefined; return ( ); } ``` Complete setup files: - [README.md](/charts/docs/examples/bar-comparison/README.md) - [LICENSE](/charts/docs/examples/bar-comparison/LICENSE) ## Horizontal bars Use `layout="vertical"` for horizontal bars, with numeric X and categorical Y axes. ## Complete Horizontal bars consumer Workshop hours with a numeric X axis and categorical Y axis. 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 { WorkshopHoursChart } from "./examples/bar-horizontal/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/bar-horizontal/example.tsx ```tsx "use client"; import * as Chart from "@kind-ui/charts"; import "@kind-ui/charts/styles.css"; const data = [ { period: "Design ops", hours: 42 }, { period: "Research", hours: 36 }, { period: "Prototyping", hours: 28 }, { period: "Testing", hours: 24 }, { period: "Review", hours: 18 }, ]; const config = { hours: { label: "Hours", color: "#14a39a", formatValue: (value) => `${value} h` }, } satisfies Chart.SeriesConfig; export function WorkshopHoursChart() { return ( ); } ``` Complete setup files: - [README.md](/charts/docs/examples/bar-horizontal/README.md) - [LICENSE](/charts/docs/examples/bar-horizontal/LICENSE) ## Materials Choose plain, paper, clay or glow. Set corner rounding with `radius`; stack caps are consumer-owned. Plot clipping can trim decorative halos. ## Complete Materials consumer Plain, paper, clay and glow on native rounded 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 { MaterialBarChart } from "./examples/bar-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/bar-materials/example.tsx ```tsx "use client"; import * as Chart from "@kind-ui/charts"; import "@kind-ui/charts/styles.css"; const data = [ { period: "North", orders: 124 }, { period: "East", orders: 186 }, { period: "Central", orders: 238 }, { period: "West", orders: 162 }, { period: "South", orders: 208 }, ]; const config = { orders: { label: "Orders", color: "#733bff" }, } satisfies Chart.SeriesConfig; export function MaterialBarChart({ material = "paper" }: { material?: Chart.BarMaterial } = {}) { return ( ); } ``` Complete setup files: - [README.md](/charts/docs/examples/bar-materials/README.md) - [LICENSE](/charts/docs/examples/bar-materials/LICENSE) ## Category emphasis `BarChart emphasis="category"` dims other categories during hover or keyboard inspection. `Root emphasis="none"` disables it. Touch inspection does not activate hover emphasis. Eligibility requires compatible values and marks in every visible series; see `BarSeries` below. ## API reference Focused props for explicit composition. Recharts owns grouping, stacking and axis geometry. ### BarChart Use inside `Root` and a sized `ResponsiveContainer`.
| Prop | Type | Default | Description | | --- | --- | --- | --- | | `data` | `Row[]` | — | Consumer-owned category observations. | | `animate` | `boolean \| BarAnimation` | `false` | Reveal from numeric zero or nearest domain edge; respects reduced motion. | | `emphasis` | `"none" \| "category"` | `"none"` | Opt in to eligible category comparisons. | | `layout` | `"horizontal" \| "vertical"` | `"horizontal"` | `"horizontal"` draws columns; `"vertical"` draws horizontal bars. | | `barGap` | `number \| string` | `4` | Gap between grouped bars. | | `barCategoryGap` | `number \| string` | `"10%"` | Space between category groups. | | `stackOffset` | `"none" \| "expand" \| "wiggle" \| "silhouette" \| "sign" \| "positive"` | `"none"` | Native stack calculation; use `"sign"` for separate positive/negative stacks. | | `accessibilityLayer` | `boolean` | `true` in Recharts | Native keyboard inspection; explicit in these components. | | `aria-label` / `aria-labelledby` | `string` | — | Accessible plot name. | | `margin` | `{ top?: number; right?: number; bottom?: number; left?: number }` | `5` each side | Space around plot parts. | | `children` | `ReactNode` | — | Explicit axes, series, grid and tooltip. |
Entrance stops on interaction or data/geometry changes. Remount to replay. `BarAnimation` accepts `revealDurationMs` (default `1000`), `revealEasing` and `hoverTransition`. See [Recharts BarChart](https://recharts.github.io/en-US/api/BarChart/). ### BarSeries String value keys identify config entries; controlled numeric keys or accessors require `seriesKey`.
| Prop | Type | Default | Description | | --- | --- | --- | --- | | `dataKey` | `string \| number \| ((row: Row) => unknown)` | Required | Value field or accessor. | | `seriesKey` | `string` | String `dataKey` | Metadata and visibility identity. | | `stackId` | `string \| number` | — | Stack series with matching axis IDs. | | `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Native-rectangle finish; custom paint takes precedence. | | `emphasisKey` | `(payload: unknown) => string \| number \| undefined` | — | Unique category identity; does not make invalid values or custom shapes eligible. | | `radius` | `number \| [number, number, number, number]` | `0` | Native corner rounding: top-left, top-right, bottom-right, bottom-left. | | `maxBarSize` | `number` | — | Limit each bar's thickness. | | `fill` | `string` | Config color | Native fill override; Cells can customize individual bars. | | `hide` | `boolean` | `false` | Native hide and Root selection both apply. | | `children` | `ReactNode` | — | Native Cells, LabelList and ErrorBar composition. |
Category emphasis requires native rectangles, chart-level rows, string value keys and finite non-baseline values. Use unique string categories or unique, defined `emphasisKey` results for every row. Missing, null, zero/baseline or range values, per-series data, custom shapes and non-boolean `activeBar` disable category emphasis for the chart. Hidden peers do not block eligibility. Finishes are static. Native per-series animation is disabled. Custom `shape`, `filter` or non-boolean `activeBar` owns paint. See [Recharts Bar](https://recharts.github.io/en-US/api/Bar/) and [material support](/charts/docs/guides/materials/). ## 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/). --- # BoxPlot Compare distributions with explicit quartiles, whiskers and outliers. ## Complete BoxPlot consumer Compare three caller-computed response time distributions, in milliseconds. 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 { ResponseTimeBoxPlot } from "./examples/box-plot/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/box-plot/example.tsx ```tsx "use client"; import * as Chart from "@kind-ui/charts"; import "@kind-ui/charts/styles.css"; const data = [ { period: "Search", lowerWhisker: 18, q1: 32, median: 44, q3: 61, upperWhisker: 84, outlier: 112, }, { period: "Checkout", lowerWhisker: 26, q1: 46, median: 68, q3: 88, upperWhisker: 126, outlier: 154, }, { period: "Profile", lowerWhisker: 12, q1: 22, median: 31, q3: 46, upperWhisker: 72, outlier: 98, }, ]; const config = { latency: { label: "Response time · ms", color: "#733bff" }, } satisfies Chart.SeriesConfig; export function ResponseTimeBoxPlot() { return ( dataKey={(row) => ({ lowerWhisker: row.lowerWhisker, q1: row.q1, median: row.median, q3: row.q3, upperWhisker: row.upperWhisker, outliers: [row.outlier], })} seriesKey="latency" barSize={32} strokeWidth={1.5} fillOpacity={0.28} /> { const row = payload?.[0]?.payload as (typeof data)[number] | undefined; if (!active || !row) return null; return (
{label}
Lower whisker
{row.lowerWhisker} ms
Q1
{row.q1} ms
Median
{row.median} ms
Q3
{row.q3} ms
Upper whisker
{row.upperWhisker} ms
Outlier
{row.outlier} ms
); }} />
); } ``` Complete setup files: - [README.md](/charts/docs/examples/box-plot/README.md) - [LICENSE](/charts/docs/examples/box-plot/LICENSE) ## Usage Supply caller-computed quartiles, whiskers and outliers to `BoxPlotSeries`. ```tsx title="Composition" dataKey={(row) => ({ lowerWhisker: row.lowerWhisker, q1: row.q1, median: row.median, q3: row.q3, upperWhisker: row.upperWhisker, outliers: [row.outlier], })} seriesKey="latency" barSize={32} /> active && payload?.[0] ? (
Median: {payload[0].payload.median} ms
) : null } />
``` The tooltip reads the original row from `payload` to label statistics; native range values alone describe only the enclosing extent. ## Summary contract Supply finite ordered statistics: `lowerWhisker <= q1 <= median <= q3 <= upperWhisker`. Outliers must be finite and strictly outside the whiskers; duplicates remain separate, possibly overlapping marks. Numeric domains must include every outlier; native domain inference includes them. Compute quartiles, whiskers and outliers before rendering; Kind chooses no statistical convention. Boxes span Q1–Q3, lines mark medians and whiskers, and circles show outliers. Equal quartiles collapse to a line. Equal whiskers, zero and negative values are valid. Missing summaries or empty data draw no marks; malformed present summaries throw. ## Collapsed and missing summaries Stable and Zero have collapsed summaries; Pending has no mark but retains its category label. Variable includes negative values and outliers. ## Complete Collapsed and missing summaries consumer Equal quartiles, zero, null and negative values retain their native semantics. 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 { EdgeCaseBoxPlot } from "./examples/box-plot-edge-cases/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/box-plot-edge-cases/example.tsx ```tsx "use client"; import * as Chart from "@kind-ui/charts"; import "@kind-ui/charts/styles.css"; type Row = { period: string; summary: Chart.BoxPlotSummary | null }; const data: Row[] = [ { period: "Stable", summary: { lowerWhisker: 5, q1: 5, median: 5, q3: 5, upperWhisker: 5 } }, { period: "Zero", summary: { lowerWhisker: 0, q1: 0, median: 0, q3: 0, upperWhisker: 0 } }, { period: "Pending", summary: null }, { period: "Variable", summary: { lowerWhisker: -12, q1: -5, median: 1, q3: 8, upperWhisker: 18, outliers: [-20, 24] }, }, ]; const config = { change: { label: "Change · percentage points", color: "#16756c" }, } satisfies Chart.SeriesConfig; export function EdgeCaseBoxPlot() { return ( dataKey="summary" seriesKey="change" barSize={28} strokeWidth={2.5} /> { const row = payload?.[0]?.payload as Row | undefined; if (!active || !row?.summary) return null; const summary = row.summary; return (
{label}
Lower whisker
{summary.lowerWhisker} pp
Q1
{summary.q1} pp
Median
{summary.median} pp
Q3
{summary.q3} pp
Upper whisker
{summary.upperWhisker} pp
Outliers
{summary.outliers?.join(", ") || "None"}
); }} />
); } ``` Complete setup files: - [README.md](/charts/docs/examples/box-plot-edge-cases/README.md) - [LICENSE](/charts/docs/examples/box-plot-edge-cases/LICENSE) ## Horizontal distributions Use `layout="vertical"` with numeric X and categorical Y axes for horizontal boxes. ## Complete Horizontal distributions consumer Response time summaries mapped to a numeric X axis. 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 { HorizontalResponseTimeBoxPlot } from "./examples/box-plot-horizontal/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/box-plot-horizontal/example.tsx ```tsx "use client"; import * as Chart from "@kind-ui/charts"; import "@kind-ui/charts/styles.css"; const data = [ { period: "Search", lowerWhisker: 18, q1: 32, median: 44, q3: 61, upperWhisker: 84, outlier: 112, }, { period: "Checkout", lowerWhisker: 26, q1: 46, median: 68, q3: 88, upperWhisker: 126, outlier: 154, }, { period: "Profile", lowerWhisker: 12, q1: 22, median: 31, q3: 46, upperWhisker: 72, outlier: 98, }, ]; const config = { latency: { label: "Response time · ms", color: "#733bff" }, } satisfies Chart.SeriesConfig; export function HorizontalResponseTimeBoxPlot() { return ( `${value}`} axisLine={false} tickLine={false} tickMargin={10} height={44} /> dataKey={(row) => ({ lowerWhisker: row.lowerWhisker, q1: row.q1, median: row.median, q3: row.q3, upperWhisker: row.upperWhisker, outliers: [row.outlier], })} seriesKey="latency" barSize={32} strokeWidth={1.5} fillOpacity={0.28} /> { const row = payload?.[0]?.payload as (typeof data)[number] | undefined; if (!active || !row) return null; return (
{label}
Lower whisker
{row.lowerWhisker} ms
Q1
{row.q1} ms
Median
{row.median} ms
Q3
{row.q3} ms
Upper whisker
{row.upperWhisker} ms
Outlier
{row.outlier} ms
); }} />
); } ``` Complete setup files: - [README.md](/charts/docs/examples/box-plot-horizontal/README.md) - [LICENSE](/charts/docs/examples/box-plot-horizontal/LICENSE) ## Materials Choose plain, paper, clay or glow. Explicit `filter` or `style.filter` overrides the finish; custom shapes can compose `BoxPlotMark` directly. ## Complete Materials consumer Plain, paper, clay and glow finishes preserve summary coordinates. 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 { MaterialResponseTimeBoxPlot } from "./examples/box-plot-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/box-plot-materials/example.tsx ```tsx "use client"; import * as Chart from "@kind-ui/charts"; import "@kind-ui/charts/styles.css"; const data = [ { period: "Search", lowerWhisker: 18, q1: 32, median: 44, q3: 61, upperWhisker: 84, outlier: 112, }, { period: "Checkout", lowerWhisker: 26, q1: 46, median: 68, q3: 88, upperWhisker: 126, outlier: 154, }, { period: "Profile", lowerWhisker: 12, q1: 22, median: 31, q3: 46, upperWhisker: 72, outlier: 98, }, ]; const config = { latency: { label: "Response time · ms", color: "#733bff" }, } satisfies Chart.SeriesConfig; export function MaterialResponseTimeBoxPlot({ material = "paper", }: { material?: "plain" | "paper" | "clay" | "glow"; } = {}) { return ( dataKey={(row) => ({ lowerWhisker: row.lowerWhisker, q1: row.q1, median: row.median, q3: row.q3, upperWhisker: row.upperWhisker, outliers: [row.outlier], })} seriesKey="latency" barSize={32} strokeWidth={1.5} fillOpacity={0.28} material={material} /> { const row = payload?.[0]?.payload as (typeof data)[number] | undefined; if (!active || !row) return null; return (
{label}
Lower whisker
{row.lowerWhisker} ms
Q1
{row.q1} ms
Median
{row.median} ms
Q3
{row.q3} ms
Upper whisker
{row.upperWhisker} ms
Outlier
{row.outlier} ms
); }} />
); } ``` Complete setup files: - [README.md](/charts/docs/examples/box-plot-materials/README.md) - [LICENSE](/charts/docs/examples/box-plot-materials/LICENSE) ## API reference Focused props for explicit range-Bar composition. BoxPlot uses public axis-scale hooks; it has no configured wrapper. ### BoxPlotChart
| Prop | Type | Default | Description | | --- | --- | --- | --- | | `data` | `Row[]` | — | Consumer-owned observations containing summaries or accessor inputs. | | `layout` | `"horizontal" \| "vertical"` | `"horizontal"` | Native layout; `"vertical"` gives horizontal boxes with numeric X and categorical Y axes. | | `animate` | `boolean \| BarAnimation` | `false` | Chart-owned clipped entrance; respects reduced motion. Native per-series animation is disabled. | | `accessibilityLayer` | `boolean` | `true` in Recharts | Native keyboard inspection. Set explicitly in these components. | | `aria-label` / `aria-labelledby` | `string` | — | Accessible plot name. | | `children` | `ReactNode` | — | Explicit series, axes, grid and tooltip. |
Custom range shapes retain inspection but cannot use Bar category emphasis. See [Recharts BarChart](https://recharts.github.io/en-US/api/BarChart/). ### BoxPlotSeries
| Prop | Type | Default | Description | | --- | --- | --- | --- | | `dataKey` | `(keyof Row & string) \| ((row: Row) => BoxPlotSummary \| null \| undefined)` | Required | Direct property name or accessor; nested string paths are not interpreted. | | `seriesKey` | `string` | Required | Root metadata, color and visibility identity, independent of the generated range accessor. | | `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Static mark finish. Explicit filters and custom shapes retain ownership. | | `barSize` | `number \| string` | Native automatic sizing | Category-axis width; numbers are pixels, native percentage strings are also accepted. | | `outlierRadius` | `number` | `3` | Radius of each outlier circle in screen pixels. | | `shape` | `(props: BoxPlotShapeProps) => ReactNode` | Built-in mark | Receives original summary, native Bar shape props and mapped coordinates. Owns returned markup. | | `markProps` | SVG group props, excluding `children` | — | Attributes, styles, handlers and refs forwarded to each present mark group. | | `xAxisId` / `yAxisId` | `string \| number` | `0` | Native axes used to map statistical values. | | `hide` | `boolean` | `false` | Native hiding; Root controlled visibility also applies. |
Stacking, `minPointSize`, background, radius and `activeBar` are excluded. Cells, labels and handlers remain available. LabelList positions refer to the enclosing range. See [Recharts Bar](https://recharts.github.io/en-US/api/Bar/). ### BoxPlotMark Renders caller-owned screen coordinates; it neither computes statistics nor maps domains.
| Prop | Type | Default | Description | | --- | --- | --- | --- | | `coordinates` | `BoxPlotSummary` | Required | Already mapped screen coordinates; these can reverse on a vertical numeric scale. | | `center` | `number` | Required | Category-axis center in pixels. | | `size` | `number` | Required | Category-axis mark width in pixels. | | `orientation` | `"vertical" \| "horizontal"` | `"vertical"` | Direction of the quantitative geometry. | | `outlierRadius` | `number` | `3` | Screen-space circle radius. | | `material` | `"plain" \| "paper" \| "clay" \| "glow"` | `"plain"` | Static finish; explicit group filters take precedence. | | `strokeWidth` | SVG stroke width | `1.5` | Native stroke width; explicit SVG paint stays consumer-owned. |
### BoxPlotSummary and helpers
| Prop | Type | Default | Description | | --- | --- | --- | --- | | `lowerWhisker` / `upperWhisker` | `number` | Required | Caller-defined finite whisker endpoints. | | `q1` / `q3` | `number` | Required | Caller-computed finite quartiles, ordered within whiskers. | | `median` | `number` | Required | Finite value between Q1 and Q3. | | `outliers` | `readonly number[]` | — | Finite observations strictly outside the supplied whiskers. |
`validateBoxPlotSummary` returns valid input unchanged, returns null for missing input, and throws for malformed summaries. `boxPlotExtent` returns the validated minimum/maximum including outliers. Neither mutates input. ## 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/). --- # Combo Chart Combine bars, lines and areas to compare related measures. ## Complete Production and capacity consumer Shipped units, available capacity and a production target on one numeric 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 { ProductionComboChart } from "./examples/combo/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/combo/example.tsx ```tsx "use client"; import * as Chart from "@kind-ui/charts"; import "@kind-ui/charts/styles.css"; const data = [ { period: "Jan", capacity: 960, shipped: 640, target: 700 }, { period: "Feb", capacity: 960, shipped: 730, target: 720 }, { period: "Mar", capacity: 1040, shipped: 780, target: 760 }, { period: "Apr", capacity: 1040, shipped: 860, target: 820 }, { period: "May", capacity: 1120, shipped: 920, target: 880 }, { period: "Jun", capacity: 1120, shipped: 1020, target: 960 }, ]; const config = { capacity: { label: "Capacity · units", color: "#b2a0ef", formatValue: (value) => `${value} units`, }, shipped: { label: "Shipped · units", color: "#733bff", formatValue: (value) => `${value} units` }, target: { label: "Target · units", color: "#087f78", formatValue: (value) => `${value} units` }, } satisfies Chart.SeriesConfig; export function ProductionComboChart() { return ( ); } ``` Complete setup files: - [README.md](/charts/docs/examples/combo/README.md) - [LICENSE](/charts/docs/examples/combo/LICENSE) ## Usage Combine `BarSeries`, `AreaSeries` and `LineSeries` in one plot. ```tsx title="Composition" ``` Choose each series’ interpolation and paint explicitly. `ComboChart` uses native composed-chart props, without Line’s configured `series` or `xDataKey` shortcuts. ## Stacks and two axes Revenue bars share stack and axis IDs; margin uses a separate percentage axis. Compare each measure on its own scale; matching vertical positions do not mean equal values. ## Complete Revenue and margin consumer Stacked retail and wholesale revenue in USD, with margin percentage on a separate right axis. 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 { RevenueMarginChart } from "./examples/combo-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/combo-stacked/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: "Jan", retail: 42000, wholesale: 26000, margin: 18 }, { period: "Feb", retail: 47000, wholesale: 28000, margin: 20 }, { period: "Mar", retail: 44000, wholesale: 34000, margin: 19 }, { period: "Apr", retail: 52000, wholesale: 31000, margin: 23 }, { period: "May", retail: 58000, wholesale: 36000, margin: 25 }, { period: "Jun", retail: 63000, wholesale: 39000, margin: 27 }, ]; const dollars = (value: Chart.TooltipValueType) => typeof value === "number" ? `$${value.toLocaleString("en-US")}` : String(value); const config = { retail: { label: "Retail · USD", color: "#733bff", formatValue: dollars }, wholesale: { label: "Wholesale · USD", color: "#b2a0ef", formatValue: dollars }, margin: { label: "Margin · %", color: "#087f78", formatValue: (value) => `${value}%` }, } satisfies Chart.SeriesConfig; export function RevenueMarginChart() { const [visibleSeries, setVisibleSeries] = useState(["retail", "wholesale", "margin"]); return ( `$${value / 1000}k`} width="auto" axisLine={false} tickLine={false} tickMargin={8} /> `${value}%`} width="auto" axisLine={false} tickLine={false} tickMargin={8} /> ); } ``` Complete setup files: - [README.md](/charts/docs/examples/combo-stacked/README.md) - [LICENSE](/charts/docs/examples/combo-stacked/LICENSE) Legend toggles recalculate visible stacks. Matching `stackId` values stack compatible series on matching axes; they do not merge families. Normalized stacks require explicit axis and tooltip formatting. ## Family motion Choose independent, shared or line-only timing. Independent mode reveals bars over 1000ms and the line over 600ms, showing Area immediately. The example remounts to replay. ## Complete Family entrances consumer Independent, shared or line-only entrances with chart-owned motion. 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 { ProductionMotionChart } from "./examples/combo-motion/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/combo-motion/example.tsx ```tsx "use client"; import * as Chart from "@kind-ui/charts"; import "@kind-ui/charts/styles.css"; const data = [ { period: "Jan", capacity: 960, shipped: 640, target: 700 }, { period: "Feb", capacity: 960, shipped: 730, target: 720 }, { period: "Mar", capacity: 1040, shipped: 780, target: 760 }, { period: "Apr", capacity: 1040, shipped: 860, target: 820 }, { period: "May", capacity: 1120, shipped: 920, target: 880 }, { period: "Jun", capacity: 1120, shipped: 1020, target: 960 }, ]; const config = { capacity: { label: "Capacity · units", color: "#b2a0ef", formatValue: (value) => `${value} units`, }, shipped: { label: "Shipped · units", color: "#733bff", formatValue: (value) => `${value} units` }, target: { label: "Target · units", color: "#087f78", formatValue: (value) => `${value} units` }, } satisfies Chart.SeriesConfig; type Entrance = "independent" | "together" | "lineOnly"; const entrances = { independent: { areaReveal: false, barReveal: { revealDurationMs: 1000 }, lineReveal: { revealDurationMs: 600 }, }, together: { revealDurationMs: 900 }, lineOnly: { areaReveal: false, barReveal: false, lineReveal: { revealDurationMs: 900 } }, } satisfies Record; 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 ( {selectedSeries ? `Highlighted: ${config[selectedSeries as keyof typeof config].label}` : "All series"} ); } ``` 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 (
`${value} MWh`} />
); } ``` 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.