# Consensus Meter

> How a group of raters lean, one segment per rating, and where their low, average, and high targets sit against the current value.

- Name: `consensus-meter`
- Status: beta
- Categories: charts
- File: the install writes `consensus-meter.tsx` to your `ui` alias, `@/components/ui` by default.
- Other registry items: `@arbokui/badge`, `@arbokui/morph-label`, `@arbokui/motion-timing`, `@arbokui/rolling-number`, `@arbokui/skeleton`. The install adds them.
- Gallery: https://arbokui.com/components/consensus-meter
- Registry item: https://arbokui.com/r/consensus-meter.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/consensus-meter
   ```

## Use it for

- Showing how analysts or reviewers lean, and where their price or score targets sit against today's value.
- Pass counts for the negative, neutral, and positive sides. Pass targets to add the track of low, average, and high.
- Pass verdict for the overall call. It turns green or red when most ratings lean one way.
- Change counts or targets, as from tabs, and the counts roll, the segments grow, shrink, and change color in place, the marks glide, and each label glides to its new column.
- Pass noun, like { one: "analyst", other: "analysts" }, for the count.

## Avoid it for

- A rating out of five stars or a single score. Use Progress or a plain number.
- More than three sides. The split has negative, neutral, and positive only.
- Values over time. Use LineChart.

## Props

- `counts`: `{ negative: number; neutral: number; positive: number }`. How many ratings fall on each side. Up to 40 draw one segment each. More draw one segment per side, sized by its share. Counts below 0 count as 0, and parts round to whole ratings.
- `targets`: `{ low: number; average: number; high: number; current: number }`. The targets and the current value. The track always fits the current value, even outside the targets. A low above the high is read the other way round.
- `verdict`: `ReactNode`. The overall call, shown in a badge. Green when 60% or more lean positive, red when 60% or more lean negative. Its words morph when they change, and long words are cut short.
- `aria-label`: `string`. Names the meter. TypeScript requires aria-label or aria-labelledby.
- `valueFormat`: `Intl.NumberFormatOptions`. Default `US dollars, with cents on every target when any has them, and short form like $2.35M from $100,000 up`. Formats the targets.
- `locale`: `string`. Default `"en-US"`. The locale for numbers. Fixed, so server and browser print the same text.
- `labels`: `{ negative?, neutral?, positive?, low?, average?, current?, high?: string }`. Default `Bearish, Neutral, Bullish, Low, Average, Current, High`. The words for each side and each target.
- `noun`: `{ one: string; other: string }`. Default `{ one: "rating", other: "ratings" }`. What one rating is called, for the count.
- `loading`: `boolean`. Default `false`. Shows placeholders at the same size.
- `emptyText`: `ReactNode`. Default `"No ratings yet"`. Shown when every count is 0.
- `className`: `string`. Extra classes. Later classes win.

## Accessibility

- Renders a \<figure> named by aria-label or aria-labelledby. TypeScript requires one.
- Every count and every target is printed as text, so nothing lives only in color. The segments and the track are hidden from screen readers, and a hidden line reads each target and how far it sits from the current value, which sighted people see on hover or tap.
- Each side's color sits beside its word: Bearish, Neutral, Bullish. The targets differ by shape: plain dots for the ends, rings for the average and the current value. The average and the line to it take the color of the lean, up or down. With forced colors, the bar, track, and marks take system colors, so they stay visible.
- Hovering a side lifts its segments and fades the others. Hovering a target grows its mark, fades the others, and swaps its word for how far it sits from the current value. On touch, a tap does the same, and a tap anywhere but on a part lets go. Each side has a strip 26px tall over the thin bar, and each mark a 24px target.
- Once on screen, the bar fills from the start and each target glides out from the current value. When the data changes, counts roll, segments resize, and marks and labels glide. With motion off, it all shows at once. Right to left, the labels and the track both run from the right.
- Loading and the empty text are read out through a status line that stays on the page.

## Examples

### Ratings and targets

```tsx
<ConsensusMeter
  aria-label="Analyst consensus"
  counts={{ negative: 0, neutral: 1, positive: 6 }}
  targets={{ low: 375, average: 398.57, high: 460, current: 282.32 }}
  verdict="Strong buy"
  noun={{ one: "analyst", other: "analysts" }}
/>
```

### The split alone

```tsx
<ConsensusMeter aria-label="Reviews" counts={{ negative: 3, neutral: 4, positive: 2 }} verdict="Mixed" />
```

### While it loads

```tsx
<ConsensusMeter aria-label="Analyst consensus" counts={data?.counts ?? { negative: 0, neutral: 0, positive: 0 }} targets={data?.targets} loading={isLoading} />
```
