# HeartbeatBlip (/docs/charts/heartbeat-blip)

HeartbeatBlip draws one ECG-style spike per event in a rolling window, 60 seconds by default, because liveness questions
are about the last minute. In the interactive entry the trace sweeps left in real time and a new event blips in at the
right edge, so the rate you see **is** the event rate. Every spike is one real event, never a synthesized pulse on a
timer, and an empty window leaves a flat baseline: the down signal is carried by shape, not color.

Pass `now` from your data layer. The static entry never calls `Date.now()`, because a server render and the client
hydrate would disagree and mismatch.

```tsx
<HeartbeatBlip events={[97000, 92000, 85000, 70000, 55000, 48000]} now={100000} title="Requests" width={90} />
```

## Install

```tsx
import { HeartbeatBlip } from "@microcharts/react/heartbeat-blip";

// pass 'now' from your data layer — never Date.now() in a server render
<HeartbeatBlip events={eventTimestamps} now={serverNow} title="Requests" />
```

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 { HeartbeatBlip } from "@microcharts/react/heartbeat-blip/interactive";

<HeartbeatBlip
  events={eventTimestamps}
  now={serverNow}
/>
```

## Motion, and reduced motion

The trace advances in real time, so old spikes drift left and new events enter at the right. This is the deliberate
idle-loop exception, allowed because the loop parameter (elapsed time) is the datum. It pauses off-screen through a
shared observer, and under `prefers-reduced-motion` it does not sweep at all: the static strip re-renders on each data
change instead, carrying the same information discretely. **A flat service never gets a fake pulse.** When the window
empties, the baseline goes flat and stays flat.

## When to use it

Use it for the liveness of a service or stream, a request rate in a header, or per-service liveness in a status table.
For exact event counts use `Seismogram` or `EventTimeline`, for a continuous level `BreathingDot`, and for long-term
trends `Sparkline`.

## Sizing

`width` and `height` are viewBox units that also set the rendered pixel box — they default to `60 × 16`, sized to sit in
a status table row or a header. Omit them and drive the width from CSS to fill a column; the viewBox keeps the aspect
ratio. `window` sets how much time the strip covers, which changes the density of the trace rather than its box.

## Variants

```tsx
<HeartbeatBlip events={events} now={serverNow} label="count" />
<HeartbeatBlip events={[]} now={serverNow} />
```

```tsx
<HeartbeatBlip
  events={[97000, 92000, 85000]}
  now={100000}
  label="count"
  format={{ useGrouping: false }}
  locale="de-DE"
/>
```

`format`/`locale` only reach the `label="count"` numeral — the accessible summary's count is a plain integer (never run
through the formatter), and the in-chart spikes never carry text, so there's nothing else to localize.

## Edge cases

```tsx
<HeartbeatBlip events={[]} now={100000} title="Requests" />
```

```tsx
<HeartbeatBlip events={[97000, Number.NaN, 92000]} now={100000} title="Requests" />
```

```tsx
// window defaults to 60s; only the in-window events draw a spike
<HeartbeatBlip events={[95000, 10000]} now={100000} title="Requests" />
```

## Four homes

**In a sentence**

```tsx
<p>
  Request liveness{" "}
  <span className="mc-inline">
    <HeartbeatBlip events={eventTimestamps} now={serverNow} width={90} height={16} summary={false} />
  </span>{" "}
  — six events in the last minute.
</p>
```

**In a table cell**

```tsx
<td><HeartbeatBlip events={svc.events} now={serverNow} width={72} height={16} /></td>
```

**In a KPI card**

```tsx
<div className="kpi"><span className="figure">6</span><HeartbeatBlip events={eventTimestamps} now={serverNow} width={200} height={30} /></div>
```

**In a tab header**

```tsx
<button className="tab">api <HeartbeatBlip events={eventTimestamps} now={serverNow} width={44} height={14} /></button>
```

## Accessibility

The accessible name is the count, the window, and the time since the last event — **"3 events in the last minute; last
3s ago."** — or **"No events in the last minute."** when the trace is flat. Down and no-data are different states, and
the summary distinguishes them. The trace sweeps only when motion is allowed and the chart is on-screen. The live region
announces on data change, and there is no per-spike navigation, because the spikes are transient and the summary is the
record.

This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; `readout={false}` drops the chip and keeps everything else.

## Props

| Prop | Type | Description |
| --- | --- | --- |
| `events` (required) | `number[]` | Event timestamps (ms). |
| `window` | `number` | The visible recent window in ms (default 60000). |
| `now` | `number` | Explicit clock — defaults to the latest event (SSR-safe). |
| `label` | `"count" \| "none"` | Event-count numeral at the right. |
| `fontSize` | `number` | Type size of the count numeral, 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).
