# BalanceBeam (/docs/charts/balance-beam)

BalanceBeam takes two values and tilts a beam toward the heavier one, so which side wins reads instantly. Inflow is 620,
outflow is 480, and the beam tilts toward inflow. The angle grows with the imbalance, but it **saturates** at `maxTilt`
degrees: past that point a steeper beam would imply a precision the eye can't extract, so read the tilt as direction
plus rough magnitude. The two weights are area-true — their area, not their width, is proportional to value. For a
precise comparison, use `PairedBars` or `Delta`.

```tsx
<BalanceBeam
  data={[
    { label: "Inflow", value: 620 },
    { label: "Outflow", value: 480 },
  ]}
  label="values"
  title="Cash flow"
  width={80}
  height={30}
/>
```

## Install

```tsx
import { BalanceBeam } from "@microcharts/react/balance-beam";

<BalanceBeam data={[{ label: "Inflow", value: 620 }, { label: "Outflow", value: 480 }]} />
```

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 { BalanceBeam } from "@microcharts/react/balance-beam/interactive";

<BalanceBeam
  data={[{ label: "Inflow", value: 620 }, { label: "Outflow", value: 480 }]}
/>
```

## When to use it

Use it for a buy vs sell or in vs out read in a sentence, a pro vs con weight in a KPI card, or an A-vs-B pair where
direction is the point. For exact ratios use PairedBars or Delta, for more than two items MiniBar, and for trends a
time-series chart.

## Sizing

**round weights + values**

```tsx
<BalanceBeam data={pair} shape="round" label="values" />
```

**balanced reads level**

```tsx
<BalanceBeam data={[{ label: "A", value: 500 }, { label: "B", value: 500 }]} />
```

## Variants

`mode="ratio"` is the default, because most two-sided comparisons are share-of-whole questions. `mode="difference"`
scales the imbalance by a shared `domain`, so rows on the same scale tilt comparably. Square weights are the default
because circles under-read area at this size; `shape="round"` swaps them.

```tsx
<BalanceBeam
  data={[{ label: "Inflow", value: 620 }, { label: "Outflow", value: 480 }]}
  shape="round"
  label="values"
/>
<BalanceBeam data={[{ label: "A", value: 500 }, { label: "B", value: 500 }]} />
```

`format` also takes `Intl.NumberFormatOptions` — with a `locale`, both the announced values and the in-chart numerals
(`label="values"`) follow that locale's own grouping and decimal marks.

```tsx
<BalanceBeam
  data={[{ label: "Inflow", value: 62000 }, { label: "Outflow", value: 48000 }]}
  label="values"
  format={{ maximumFractionDigits: 0 }}
  locale="de-DE"
/>
```

## Edge cases

```tsx
<BalanceBeam data={[{ label: "A", value: 500 }, { label: "B", value: 500 }]} />
```

```tsx
<BalanceBeam data={[{ label: "A", value: 0 }, { label: "B", value: 0 }]} />
```

Equal values level the beam and report **"balanced"** rather than picking an arbitrary heavier side. Two zeros are the
same case: the beam stays level and both weights shrink to zero area.

## Four homes

**In a sentence**

```tsx
<p>
  Cash flow this month{" "}
  <span className="mc-inline">
    <BalanceBeam data={[{ label: "A", value: 620 }, { label: "B", value: 480 }]} summary={false} />
  </span>{" "}
  — inflow outweighs outflow, beam tilts right.
</p>
```

**In a table cell**

```tsx
<td>
  <BalanceBeam data={[{ label: "A", value: 620 }, { label: "B", value: 480 }]} />
</td>
```

**In a KPI card**

```tsx
<div className="kpi">
  <span className="figure">+140</span>
  <span className="unit">net inflow</span>
  <BalanceBeam data={[{ label: "A", value: 620 }, { label: "B", value: 480 }]} />
</div>
```

**In a tab header**

```tsx
<button className="tab">
  Operating <BalanceBeam data={[{ label: "A", value: 620 }, { label: "B", value: 480 }]} />
</button>
```

## Accessibility

The accessible name states both sides and the winner — **"Inflow 620 vs Outflow 480; Inflow heavier."** — or "…;
balanced." when they match. The two channels always agree: the tilt direction and the larger weight area point at the
same side. The interactive entry eases the beam to its new tilt, reveals a side's value on hover or ←/→, and announces
when the heavier side flips.

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},{label,value}]` | Exactly two items. |
| `maxTilt` | `number` | Degrees at full saturation (default 12). |
| `shape` | `"square" \| "round"` | Weight shape (default square). |
| `mode` | `"ratio" \| "difference"` | ratio = share-of-whole; difference = absolute, scaled by domain. |
| `fontSize` | `number` | Type size of the weight numerals, in viewBox units. Defaults from `height`. |
| `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).
