# LikertStrip (/docs/charts/likert-strip)

LikertStrip stacks ordinal responses on either side of a center line: disagree to the left, agree to the right. Graded
opacity encodes ordinal distance from neutral, never magnitude. Where SegmentedBar shows composition without valence,
this reads the lean and how hard it leans. Both neutral conventions ship. `neutral="split"` straddles the center line,
the canonical placement; `neutral="omit"` takes neutral out of the bar for a cleaner pole comparison, and its share is
still counted in the total and spoken in the accessible summary.

```tsx
<LikertStrip
  data={[
    { label: "Strongly disagree", value: 10 },
    { label: "Disagree", value: 14 },
    { label: "Neutral", value: 14 },
    { label: "Agree", value: 34 },
    { label: "Strongly agree", value: 28 },
  ]}
  title="Q1 satisfaction"
  width={220}
  height={24}
/>
```

## Install

```tsx
import { LikertStrip } from "@microcharts/react/likert-strip";

<LikertStrip
  data={[
    { label: "Strongly disagree", value: 10 },
    { label: "Disagree", value: 14 },
    { label: "Neutral", value: 14 },
    { label: "Agree", value: 34 },
    { label: "Strongly agree", value: 28 },
  ]}
  title="Q1 satisfaction"
/>
```

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 { LikertStrip } from "@microcharts/react/likert-strip/interactive";

<LikertStrip
  data={responses}
/>
```

## When to use it

Use it for survey question rows (share one scale via SparkGroup) and sentiment in cards. Avoid it past 7 levels; for
exact per-level values use MiniBar.

## Sizing

**survey rows**

```tsx
{questions.map((q) => (
  <LikertStrip key={q.id} data={q.responses} title={q.text} />
))}
```

**net score for dense tables**

```tsx
<LikertStrip data={responses} label="net" />
```

## Variants

```tsx
// the known critique of center-split neutral — supported, never silent
<LikertStrip data={responses} neutral="omit" />
<LikertStrip data={responses} label="net" />
```

## Edge cases

```tsx
// every level at 0 (or empty data) — nothing to stack, nothing to say
<LikertStrip data={[
  { label: "Disagree", value: 0 },
  { label: "Agree", value: 0 },
]} />
```

```tsx
// every response landed on the neutral level — no lean to report
<LikertStrip data={[
  { label: "Disagree", value: 0 },
  { label: "Neutral", value: 10 },
  { label: "Agree", value: 0 },
]} />
```

Empty data, or data whose values all resolve to 0, has nothing to divide into a diverging read. No bar draws, and the
accessible name says so plainly: **"No responses."** When every non-zero response lands on the neutral level, the bar is
entirely the center segment and the summary reads **"All responses neutral."** instead of forcing a lean out of no
signal. Negative counts are treated as 0 rather than pushed across the center line onto the wrong side, and the static
entry logs a dev warning when it happens.

## Four homes

**In a sentence**

```tsx
<p>
  Checkout satisfaction, Q1{" "}
  <span className="mc-inline">
    <LikertStrip data={responses} label="none" height={16} summary={false} />
  </span>{" "}
  — 62% agree, 24% disagree. Leans positive.
</p>
```

**In a table cell**

```tsx
<td>
  <LikertStrip data={q.responses} label="none" />
</td>
```

**In a KPI card**

```tsx
<div className="kpi">
  <span className="figure">62%</span>
  <span className="unit">agree, 24% disagree</span>
  <LikertStrip data={responses} label="none" width={160} />
</div>
```

**In a tab header**

```tsx
<button className="tab">
  New users <LikertStrip data={responses} label="none" width={64} />
</button>
```

## Accessibility

The accessible name carries the full valence read: **"62% agree, 24% disagree, 14% neutral. Leans positive."** A |net|
under 5 points reads "Balanced." The interactive entry steps the levels in data order and announces the response count
beside the share it was computed from: **"Disagree: 14% (14), level 2 of 5."**

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 }[]` | Ordinal levels, negative → positive. |
| `neutral` | `"split" \| "omit"` | Center-straddle or omit-from-bar (always labeled). |
| `label` | `"ends" \| "net" \| "none"` | Agree/disagree % or one signed score. |
| `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).
