# StarSpoke (/docs/charts/star-spoke)

The chart below prints five spokes from one center, each spoke's length one metric: Speed at 0.9, Cost at 0.3. That is
an entity's profile, and a row of them shows which entity in a set is the odd one out. There is no connecting polygon,
ever: the enclosed area of a radar chart misstates magnitude and changes with axis order, and the research favors
contour-free marks for spotting outliers. One `domain` governs every spoke in a glyph, so normalizing mixed-unit metrics
is the caller's job and a set of small multiples stays comparable.

```tsx
<StarSpoke
  data={[
    { label: "Speed", value: 0.9 },
    { label: "Power", value: 0.6 },
    { label: "Range", value: 0.5 },
    { label: "Cost", value: 0.3 },
    { label: "Ease", value: 0.7 },
  ]}
  dots="tips"
  title="Product profile"
  size={110}
/>
```

## Install

```tsx
import { StarSpoke } from "@microcharts/react/star-spoke";

<StarSpoke data={metrics} title="Product profile" />
```

Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.

## Try it

```tsx
import { StarSpoke } from "@microcharts/react/star-spoke/interactive";

<StarSpoke
  data={metrics}
/>
```

## When to use it

Use it for entity profiles in small multiples and for skill or capability comparison. Fewer than 3 metrics read better
as PairedBars, and precise values belong in MiniBar.

## Sizing

**small multiple**

```tsx
<StarSpoke data={row.metrics} size={28} />
```

**vs baseline**

```tsx
<StarSpoke data={metrics} compare={baseline} dots="tips" />
```

## Variants

```tsx
const metrics = [
  { label: "Speed", value: 0.9 },
  { label: "Power", value: 0.6 },
  { label: "Range", value: 0.5 },
  { label: "Cost", value: 0.3 },
  { label: "Ease", value: 0.7 },
];
const baseline = [0.5, 0.5, 0.5, 0.5, 0.5];

<StarSpoke data={metrics} compare={baseline} dots="tips" />
```

`locale` changes no in-chart mark, since tip labels are metric names rather than formatted numbers. It does localize the
extremes named in the accessible summary:

```tsx
const metrics = [
  { label: "Speed", value: 1900 },
  { label: "Power", value: 600 },
  { label: "Range", value: 500 },
];

<StarSpoke data={metrics} domain={[0, 2000]} locale="de-DE" />
```

## Edge cases

```tsx
<StarSpoke
  data={[
    { label: "Reliability", value: 0.9 },
    { label: "Throughput", value: 0.6 },
    { label: "Cost efficiency", value: 0.5 },
  ]}
  size={48}
/>
```

```tsx
<StarSpoke data={[]} title="Empty" />
```

Every spoke sits at a fixed clock position, the first at 12 o'clock and the rest clockwise, so the axis order never
shifts between instances. That fixed order plus the default-on guide spokes keep a profile readable where a metric's
label doesn't seat. Tip labels seat at the rim rather than at the value tip, so a low-value spoke never drags its label
into the hub. A label is dropped only when its estimated width exceeds the whole glyph; anything narrower is clamped
into the reserved label ring, which keeps rim labels at distinct angles.

## Four homes

**In a sentence**

```tsx
<p>
  Product profile{" "}
  <span className="mc-inline">
    <StarSpoke data={metrics} summary={false} />
  </span>{" "}
  — strong on speed, weak on cost.
</p>
```

**In a table cell**

```tsx
<td>
  <StarSpoke data={metrics} />
</td>
```

**In a KPI card**

```tsx
<div className="kpi">
  <span className="figure">0.9</span>
  <span className="unit">top dimension</span>
  <StarSpoke data={metrics} />
</div>
```

**In a tab header**

```tsx
<button className="tab">
  Product A <StarSpoke data={metrics} />
</button>
```

Best at KPI/card scale — spoke tips need room to read.

## Accessibility

The accessible name names the extremes: **"5 metrics; highest Speed (0.9), lowest Cost (0.3)."** The interactive entry
rotates focus through the spokes with ←/→, announcing each metric and value.

The interactive entry follows the shared [interaction contract](/docs/accessibility#one-interaction-contract):
arrow keys rove between units on both axes, `Home` and `End` jump to the ends, and a click, tap, `Enter` or
`Space` selects a unit — pinning its readout so it survives blur, until you select it again, press `Escape`, or
press outside the chart. On touch, a tap pins and a drag scrubs.

## Props

| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label, value }[]` | 3–8 metrics on a shared domain. |
| `dots` | `"tips" \| "none"` | `"tips"` draws endpoint dots to sharpen the outlier read. |
| `guides` | `boolean` | Full-length guide spokes (read-back scaffold). |
| `compare` | `number[]` | Muted ghost baseline spokes. |
| `labels` | `boolean` | Spoke labels at the tips (default true; drop out below size 44). |
| `size` | `number` | Star box edge in viewBox units (default 80). |
| `animate` | `boolean` | (interactive) Opt-in entrance motion when the chart mounts client-side — add `import "@microcharts/react/motion"` once. Inert on the server, on hydrated server HTML, and under `prefers-reduced-motion`. |

Plus the shared grammar — `data`, `domain`, `color`, `title`, `summary`, `format` — and the layout props (`width`, `height`, `className`, `style`) that every chart accepts. Interactive entries also share `animate` and `live`, and — wherever a chart has more than one navigable unit — `onActive`, `onSelect`, `selectedIndex` and `defaultSelectedIndex`; and — wherever the chart shows a hover value — `readout`. See [the shared grammar](/docs/quickstart#the-shared-grammar).
