# Chart frame

Name, size and control a chart with explicit family parts.

Each chart family takes `config`, an accessible name and explicit children. Config supplies labels and colors; children supply the plotted marks, axes and optional parts.

## Usage

```tsx
import { BarChart, type SeriesConfig } from "@kind-ui/charts";

const config = {
  orders: { label: "Orders", color: "#4477aa" },
} satisfies SeriesConfig;
const data = [{ day: "Mon", orders: 24 }];

<BarChart config={config} data={data} height={280} aria-label="Daily orders">
  <BarChart.XAxis dataKey="day" />
  <BarChart.YAxis />
  <BarChart.Series dataKey="orders" />
  <BarChart.Tooltip />
  <BarChart.Legend />
</BarChart>
```

Focus Print or Digital to dim its peers while preserving the bars and tooltip values.

### Grouped and stacked series

[Complete example source](https://kindui.dev/charts/docs/examples/bar-comparison/variants/grouped/example.tsx) · [Setup manifest](https://kindui.dev/charts/docs/examples/bar-comparison/package.json)

```tsx
"use client";
import { BarChart, type SeriesConfig } from "@kind-ui/charts";

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 SeriesConfig;

export function LibraryLoansChart({
  arrangement = "grouped",
}: {
  arrangement?: "grouped" | "stacked";
} = {}) {
  const stackId = arrangement === "stacked" ? "loans" : undefined;
  return (
    <figure style={{ margin: 0 }}>
      <>
        <>
          <BarChart
            legendPosition="top"
            config={config}
            interaction={{ kind: "series", mode: "focus", eligibleKeys: Object.keys(config) }}
            width="100%"
            height={280}
            data={data}
            animate
            accessibilityLayer
            emphasis="category"
            barGap={4}
            margin={{ top: 20, right: 32, bottom: 8, left: 0 }}
            aria-label="Library loans by subject and format"
          >
            <BarChart.Grid vertical={false} strokeDasharray="3 3" stroke="#ccc" />
            <BarChart.XAxis
              dataKey="period"
              axisLine={false}
              tickLine={false}
              tickMargin={16}
              height={56}
              interval="preserveStartEnd"
              minTickGap={32}
              tick={{ fill: "#666" }}
            />
            <BarChart.YAxis
              axisLine={false}
              tickLine={false}
              tickMargin={10}
              width={72}
              tick={{ fill: "#666" }}
            />
            <BarChart.Series
              dataKey="print"
              {...(stackId === undefined ? {} : { stackId })}
              maxBarSize={32}
            />
            <BarChart.Series
              dataKey="digital"
              {...(stackId === undefined ? {} : { stackId })}
              maxBarSize={32}
            />
            <BarChart.Tooltip />
            <BarChart.Legend />
          </BarChart>
        </>
      </>
    </figure>
  );
}
```

Supporting source:
- [src/main.tsx](https://kindui.dev/charts/docs/examples/bar-comparison/src/main.tsx)


## Sizing

Ordinary plots fill the available width and use a 280px height. ActivityRings uses 300px. Set `width`, `height`, `style` or `className` for your layout; explicit dimensions and inline styles take precedence over the defaults. A percentage height needs a measurable parent. Set `responsive={false}` when using fixed native chart dimensions.

`className`, `style`, native handlers and refs stay on the plot. `frameProps` supplies attributes, styles, handlers and a ref for the outer HTML frame.

## Visibility

Pass `visibleSeries` to control visibility in your own state. Focus never changes it. To request hide/show from the legend, use visibility interaction and a callback:

```tsx
import { BarChart } from "@kind-ui/charts";

<BarChart
  config={config} data={data} aria-label="Daily orders"
  interaction={{ kind: "series", mode: "visibility", eligibleKeys: Object.keys(config) }}
  visibleSeries={visibleSeries} onVisibleSeriesChange={setVisibleSeries}
>
  <BarChart.XAxis dataKey="day" />
  <BarChart.Series dataKey="orders" />
  <BarChart.Legend />
</BarChart>
```

Use `defaultVisibleSeries` for uncontrolled visibility. Controlled and default values are mutually exclusive. A callback without either value is invalid. Visibility without a callback is read-only.

Legend actions keep at least one eligible item visible. Consumer-controlled visibility can hide every item; provide an empty-result message.

<span id="interaction-and-eligibility" />

## Focus and eligibility

Focus highlights an item without hiding its data. Provide stable `eligibleKeys`, including hidden and zero-valued identities:

```tsx
import { BarChart } from "@kind-ui/charts";

<BarChart config={config} data={data} aria-label="Daily orders"
  interaction={{ kind: "series", mode: "focus", eligibleKeys: Object.keys(config) }}
>
  <BarChart.XAxis dataKey="day" />
  <BarChart.Series dataKey="orders" />
  <BarChart.Legend />
</BarChart>
```

Use `selected` with `onSelectionChange` for controlled focus, or `defaultSelected` for uncontrolled focus. Activating the same item or pressing Escape clears focus. Supported marks share legend actions unless `markActivation="none"` is set in the interaction options.

Your event handlers run first. Call `event.preventDefault()` or return `false` from `onBeforeInteraction` to cancel a shared action.

## Controls and status

Use `Content` for contextual HTML controls or status text. It mounts outside the SVG while preserving chart context, including through fragments and consumer components. Compound HTML parts mount in the browser.

```tsx
import { BarChart } from "@kind-ui/charts";

<BarChart config={config} data={data} aria-label="Daily orders">
  <BarChart.XAxis dataKey="day" />
  <BarChart.Series dataKey="orders" />
  <BarChart.Content>
    <p role="status">{data.length} daily observations</p>
  </BarChart.Content>
</BarChart>
```

`children={null}` intentionally renders no plot parts.

## Props

### Chart frame

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `nonce` | `string` | — | — |
| `visibleSeries` | `readonly string[]` | All registered items | Controlled visibility; provide onVisibleSeriesChange for legend hide/show. Hidden data retains its geometry and dimmed legend/tooltip entries. |
| `defaultVisibleSeries` | `readonly string[]` | All registered items | Initial uncontrolled visibility; mutually exclusive with visibleSeries. Use explicit visibility interaction for legend hide/show. |
| `onVisibleSeriesChange` | `((next: string[]) => void) \| ((next: string[]) => void)` | — | — |
| `config` | `Readonly<Record<string, { label?: string; color: SeriesColor; pattern?: FillPattern; icon?: React.ComponentType; legendShape?: NonNullable<React.ComponentProps<typeof Symbols>["type"]>; formatValue?: (value: TooltipValueType) => React.ReactNode; }>>` | Required | — |
| `emphasis` | `"auto" \| "none"` | "auto" | — |
| `interaction` | `ChartInteractionConfig` | Series focus; matching-legend marks | Focus dims peers by default and supported marks share legend actions. Set mode to "visibility" for visual hide/show with preserved full-data layout, or markActivation to "none" for passive marks. Explicit interaction requires kind and eligibleKeys; selected requires onSelectionChange. Legend actions retain an active item. |
| `aria-label` | `string` | — | — |
| `aria-labelledby` | `string` | — | — |
| `frameProps` | `Omit<ChartProviderProps, "children" \| "config" \| "defaultVisibleSeries" \| "emphasis" \| "interaction" \| "nonce" \| "onVisibleSeriesChange" \| "visibleSeries"> & { [key: 'data-${string}']: unknown; }` | — | Attributes, handlers and ref for the outer HTML frame. Plot props remain on the chart. |
| `children` | `string \| number \| bigint \| boolean \| Iterable<React.ReactNode> \| Promise<AwaitedReactNode> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>> \| React.ReactPortal \| null` | Required | — |

`emphasis` defaults to `"auto"`. Set `"none"` to disable Kind emphasis. Bar also supports `"category"` for safe category comparisons. Each config key supplies a `--color-{key}` CSS variable for custom marks.

## Loading

Set `loading` while data is pending. Only the plot placeholder is visible; chart-owned legends, content and instructions stay hidden until ready. The chart keeps its size, announces `loadingLabel` and preserves selection and visibility through loading.

```tsx
import { LineChart } from "@kind-ui/charts";

<LineChart data={data} config={config} loading={isPending}
  loadingLabel="Loading monthly sales" aria-label="Monthly sales">
  <LineChart.XAxis dataKey="month" />
  <LineChart.Series dataKey="sales" />
  <LineChart.Tooltip />
</LineChart>
```

Reduced motion keeps the loading preview still. Show your own message for errors or empty results.

### Loading

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `loading` | `boolean` | false | — |
| `loadingLabel` | `string` | "Loading chart" | — |
