# CometTrail (/docs/charts/comet-trail)

CometTrail plots the current value as a bright head dot, with recent points trailing behind it and fading with age. In
the interactive entry the head eases to each new value and the old head steps down into the trail, so a live stream
draws a comet and a stall goes still. Opacity encodes **age only**; the y position carries the value. The trail is
recency context, not data you have to decode.

```tsx
<CometTrail data={[40, 45, 50, 55, 60, 65, 70, 72, 75, 78, 80, 84, 87]} title="Latency" width={90} />
```

## Install

```tsx
import { CometTrail } from "@microcharts/react/comet-trail";

<CometTrail data={rollingWindow} title="Latency" />
```

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 { CometTrail } from "@microcharts/react/comet-trail/interactive";

<CometTrail
  data={rollingWindow}
/>
```

## Motion, and reduced motion

Motion happens **only on a data change**; there is no idle loop. When a new value arrives the head eases (~200 ms, the
library's canonical strong ease-out) from its old position to the new one, and the previous head steps down into the
trail. A continuous stream produces the comet; a stalled stream goes still, which is itself the signal. The head never
simulates phantom positions between updates: it eases to each real value. Under `prefers-reduced-motion` the head
repositions instantly, since the static encoding is already complete. The same holds off-screen — one shared viewport
check means the ease only runs while the chart is in view.

## When to use it

Use it for a live price or metric with a little recency context, a realtime KPI that should show momentum, or per-stream
"where is it now" in a table. For the full history or an exact multi-point comparison reach for `Sparkline` (or
`DotPlot`); for discrete events, `HeartbeatBlip`.

## Sizing

`width` and `height` are viewBox units that also set the rendered pixel box — they default to `60 × 16`, sized to sit in
a table cell or beside a line of text. Omit them and drive the width from CSS to fill a column; the viewBox keeps the
aspect ratio. `trail` controls how many recent points the tail carries, which is a density decision rather than a size
one. It is capped at 20: past that the trail stops being a trail and becomes a sparkline, which is the better tool for
the full history.

## Variants

`label="last"` is on by default, so the head always prints its number. Changing `trail` gives you more or less recency
context without changing the head read, because opacity encodes age and nothing else.

```tsx
<CometTrail data={window} trail={6} />
<CometTrail data={window} label="none" />
```

## Edge cases

```tsx
<CometTrail data={[]} title="No stream yet" />
```

```tsx
<CometTrail data={[52]} title="First reading" />
```

```tsx
// trail=20 but only 4 points exist — every point shows, no padding
<CometTrail data={[40, 55, 48, 62]} trail={20} title="Short stream" />
```

Empty data draws just the frame, with "No data." as the summary. A single point has no trail to fade: the head alone,
and "Now 52." A `trail` larger than the data never backfills or pads. It shows every point there is.

## Four homes

**In a sentence**

```tsx
<p>
  p95 latency is climbing{" "}
  <span className="mc-inline">
    <CometTrail data={rollingWindow} label="none" width={90} height={16} summary={false} />
  </span>{" "}
  — now at 87.
</p>
```

**In a table cell**

```tsx
<td><CometTrail data={rollingWindow} trail={6} label="none" width={72} height={16} /></td>
```

**In a KPI card**

```tsx
<div className="kpi"><span className="figure">87</span><CometTrail data={rollingWindow} width={200} height={36} /></div>
```

**In a tab header**

```tsx
<button className="tab">checkout <CometTrail data={rollingWindow} trail={5} label="none" width={44} height={14} /></button>
```

## Accessibility

The accessible name is the now-value and the recent trend: **"Now 62, rising over the last 3 updates."** With a single
point it is the value alone, as in the edge case above: **"Now 52."** Arrow keys step back through the trail ("3 updates
ago: 78.") and return toward now. Motion is gated on `prefers-reduced-motion`, and no reading depends on decoding the
fade.

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) | `number[]` | The rolling window, oldest → newest (last = now). |
| `trail` | `number` | Points kept visible (default 12, cap 20). |
| `label` | `"last" \| "none"` | Numeral after the head (default last). |
| `fontSize` | `number` | Type size of the numeral after the head, in viewBox units. Defaults from `height`. |

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).
