# Live line chart

> One metric as it happens: each point slides in at an even pace, the scale eases, and a Live pill pauses the view.

A live chart is where people watch traffic, latency or sign-ups as they happen, and most versions jump a step every time a point lands and redraw their axis with it. In this one each new point slides in from the right over the time it took to arrive, so the line moves at an even pace, and the end dot rides the line and pings. When the range changes the line rescales smoothly under fixed gridlines while the axis figures roll. Point at the line and the header becomes the readout: the figure rolls to that moment and “now” morphs to how long ago it was. The Live pill holds the view still while data keeps coming; when points stop arriving it says Waiting and its pulse stops. A limit can sit on it as a dashed line, its label riding the scale.

## Install

```bash
npx shadcn@latest add https://hairlineui.com/r/live-line-chart.json
```

Needs React 19, Tailwind CSS v4 and shadcn/ui theme variables (any style, light and dark). Icons from lucide-react.

- npm: none
- Hairline UI components it needs (the CLI installs them too): `number-roll`, `text-morph`
- Docs and live preview: https://hairlineui.com/ui/live-line-chart
- Registry item (source): https://hairlineui.com/r/live-line-chart.json

## Usage

```tsx
import { LiveLineChart } from "@/components/live-line-chart";

<LiveLineChart
  label="Requests"
  unit="req/s"
  data={points} // [{ t: Date.now(), value: 1096 }, …] — keep appending
  threshold={{ value: 1200, label: "Rate limit" }}
/>
```

## From a server-sent event stream

Append each reading as it arrives and keep the last couple of minutes; the chart shows its window and slides the rest.

```tsx
"use client";
import * as React from "react";
import { LiveLineChart, type LivePoint } from "@/components/live-line-chart";

export function LiveRequests() {
  const [points, setPoints] = React.useState<LivePoint[]>([]);

  React.useEffect(() => {
    const source = new EventSource("/api/metrics/requests"); // sends { t, value } once a second
    source.onmessage = (e) => {
      const point: LivePoint = JSON.parse(e.data);
      setPoints((prev) => [...prev.slice(-150), point]);
    };
    return () => source.close();
  }, []);

  return <LiveLineChart label="Requests" unit="req/s" data={points} threshold={{ value: 1200, label: "Rate limit" }} />;
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `LivePoint[]` | – | Points oldest first as { t (ms), value }; keep appending. Older than the window is left out. |
| `window` | `number` | `60000` | How much time the chart shows, in ms. |
| `label` | `ReactNode` | – | Over the figure, e.g. "Requests". |
| `unit` | `string` | – | After the figure, e.g. "req/s". |
| `format` | `Intl.NumberFormatOptions` | `{ maximumFractionDigits: 0 }` | How the figure is written. The axis uses compact numbers. |
| `locales` | `string \| string[]` | – | For number formatting. |
| `threshold` | `{ value: number; label?: string }` | – | A limit drawn as a dashed line, always kept in view. |
| `color` | `string` | `var(--chart-1)` | The line and its fill. |
| `height` | `number` | `140` | Plot height in px. |
| `defaultPaused` | `boolean` | `false` | Start paused. |
| `onPausedChange` | `(paused: boolean) => void` | – | Called when the Live pill pauses or resumes. |
| `staleAfter` | `number` | `3 × the last gap` | Ms without a new point before the pill says Waiting. |

## States

- **stream**: A new point starts one step to the right and the line slides into place, linear, over the gap since the previous point (capped at 4s); the end dot follows the line and pings while live.
- **scale**: The range is three round gridlines with 15% room; a new range eases the line (600ms) under fixed gridlines while the axis figures roll.
- **point**: Pointing at the plot snaps to the nearest point: a hairline and a dot mark it, the figure rolls to its value and “now” morphs to “12s ago”.
- **pause**: The Live pill (aria-pressed) holds the points on show; new data is kept and shown again on resume.
- **waiting**: With no new point for three times the last gap (or staleAfter), the pill morphs to Waiting, its dot turns amber and stops pulsing.

## What it handles

Live charts are usually a chart library redrawing once a second, so the line jumps and the axis flickers. This takes a growing list of points and handles the scrolling, the scale, the readout and the stalled feed.

- **Smooth scrolling**: Each point slides in over the gap it took to arrive, so the line never jumps.
- **A scale that eases**: Round gridlines that hold every value in view; when they change the line rescales and the figures roll.
- **The header is the readout**: Point at the line and the figure rolls to that moment, with how long ago it was.
- **Pause and catch up**: The Live pill holds the view still while data keeps coming; press it again to catch up.
- **A stalled feed shows**: When points stop arriving the pill says Waiting and its pulse stops.
- **Limits in view**: A threshold is drawn as a dashed amber line with its label, and always kept inside the scale.

## Replaces

a streaming chart library, a requestAnimationFrame loop, axis scaling, a hover tooltip, a live/paused indicator

## Where it fits

- **API and infrastructure dashboards**: Requests, errors or latency per second, against a rate limit or an SLO.
- **Launch and live event pages**: Sign-ups, checkouts or viewers as they arrive.
- **Trading and pricing screens**: A price or a rate over the last minute, with how far it moved.
- **IoT and monitoring panels**: A sensor reading, queue depth or CPU, with a warning line.

## Notes

- Grey in a new shadcn project? Its default theme sets --chart-1 to --chart-5 to greys. Set five colours, light and dark, as in Theming › Chart colours (/ui/theming#charts).
- The plot is decoration; a visually hidden line gives the latest value and its change, and it isn’t a live region, so a reading a second never floods a screen reader.
- The Live pill is a toggle button (aria-pressed) with its own label for pausing and resuming.
- The line is drawn in value space inside one scaled group, so a new range is a single transform transition; the slide is a second transform around it. No animation frame loop runs.
- Rising change is emerald and falling red, each with an arrow, so colour is never the only signal.
- It sizes to its own width; the figure and type step up from about 450px.
- Installs Number roll and Text morph, and adds the ui-ping keyframes. With reduced motion the line steps, nothing pings and the scale changes in place.

License: MIT (https://hairlineui.com/license#components)
