Liveline
Liveline is a real-time animated line chart component for React. One <canvas>, no dependencies beyond React 18, smooth interpolation at 60fps.
Degen mode (chart shake and particles) with momentum arrows.
I built this because every charting library I tried was either too heavy for a simple live feed, or too rigid to feel alive. Liveline does one thing: draw a line that moves smoothly as new data arrives. Everything else is opt-in.
Getting started
npm install livelineThe component fills its parent container. Set a height on the wrapper.
import { Liveline } from 'liveline'
function Chart({ data, value }) {
return (
<div style={{ height: 200 }}>
<Liveline data={data} value={value} />
</div>
)
}data is an array of { time, value } points. value is the latest number.
Two props. That's it.
Feed it data however you like. WebSocket, polling, random walk. Liveline interpolates between updates so even infrequent data looks smooth. It works for anything with a value that changes over time.
Resting heart rate. Custom formatter, exaggerated Y-axis.
Momentum
The momentum prop adds directional arrows and a glow to the live dot. Green for up, red for down, grey for flat. Pass true to auto-detect direction, or force it with "up", "down", or "flat".
Arrows fade out fully before the new direction fades in.
Value overlay
showValue renders the current value as a large number over the chart. It updates at 60fps through direct DOM manipulation, not React re-renders. Pair it with valueMomentumColor to tint the number based on direction.
60fps value overlay with momentum colouring.
Time windows
Pass a windows array to render time horizon buttons. Each entry has a label and secs value. Three styles are available via windowStyle: "default", "rounded", and "text".
<Liveline
windows={[
{ label: '1m', secs: 60 },
{ label: '5m', secs: 300 },
]}
windowStyle="rounded"
/>CPU usage with occasional spikes. Rounded time windows.
Reference line
referenceLine draws a horizontal line at a fixed value. Pass an object with value and an optional label.
Polymarket-style prediction line. "Will Bitcoin stay above $67,500?"
Orderbook
Pass an orderbook prop with bids and asks arrays to render streaming order labels behind the line. Each entry is a [price, size] tuple. Labels spawn at the bottom, drift upward, and fade out. Green for bids, red for asks. Bigger orders appear brighter.
The stream speed reacts to price momentum and orderbook churn (how much the bid/ask totals are changing). Calm markets drift slowly, volatile ones rush.
Kalshi-style orderbook stream. Bid and ask sizes float upward behind the price line.
Theming
Pass any CSS colour string to color and Liveline derives the full palette. Line, fill gradient, glow, badge, grid labels. It converts the input to HSL and generates every variant from there.
Dark theme. Same component, different colour.
More features
Everything is off by default or has sensible defaults. A few more things you can turn on:
exaggeratetightens the Y-axis range so small movements fill the full chart height. Useful for values that move in tiny increments, like the heart rate demo above.scrubshows a crosshair with time and value tooltips on hover. On by default.degenenables burst particles and chart shake on momentum swings. For when subtlety is not the goal.badgeVariant="minimal"renders a quieter white pill instead of the accent-colored default. Orbadge={false}to remove it entirely.
How it works
One <canvas>, one requestAnimationFrame loop. When a new value arrives, nothing jumps. The chart lerps toward the new state at 8% per frame (lerpSpeed). The Y-axis range, the badge, the grid labels all use the same lerp. The range snaps outward instantly when data exceeds it, so the line is never clipped. That's why it feels like one thing breathing rather than a bunch of parts updating independently.
Props
Data
| Prop | Type | Default |
|---|---|---|
| data | LivelinePoint[] | required |
| value | number | required |
Appearance
| Prop | Type | Default |
|---|---|---|
| theme | 'light' | 'dark' | 'dark' |
| color | string | '#3b82f6' |
| grid | boolean | true |
| badge | boolean | true |
| badgeVariant | 'default' | 'minimal' | 'default' |
| badgeTail | boolean | true |
| fill | boolean | true |
| pulse | boolean | true |
Features
| Prop | Type | Default |
|---|---|---|
| momentum | boolean | Momentum | true |
| scrub | boolean | true |
| exaggerate | boolean | false |
| showValue | boolean | false |
| valueMomentumColor | boolean | false |
| degen | boolean | DegenOptions | false |
Time
| Prop | Type | Default |
|---|---|---|
| window | number | 30 |
| windows | WindowOption[] | — |
| onWindowChange | (secs: number) => void | — |
| windowStyle | 'default' | 'rounded' | 'text' | — |
Crosshair
| Prop | Type | Default |
|---|---|---|
| tooltipY | number | 14 |
| tooltipOutline | boolean | true |
Orderbook
| Prop | Type | Default |
|---|---|---|
| orderbook | OrderbookData | — |
Advanced
| Prop | Type | Default |
|---|---|---|
| referenceLine | ReferenceLine | — |
| formatValue | (v: number) => string | v.toFixed(2) |
| formatTime | (t: number) => string | HH:MM:SS |
| lerpSpeed | number | 0.08 |
| padding | Padding | { top: 12, right: 80, bottom: 28, left: 12 } |
| onHover | (point: HoverPoint | null) => void | — |
| cursor | string | 'crosshair' |
| className | string | — |
| style | CSSProperties | — |
Stress testing
A chart that only looks good on calm data isn't much use. These demos throw the worst stuff I could think of at it: wild volatility, sharp direction changes, isolated spikes on flat lines, and irregular data arrival with random gaps.
Wild swings, fast updates (100ms)
Near-flat, ultra-low volatility, exaggerate (150ms)
Chaotic, huge spikes (80ms)
Sharp reversals are the classic breaking point. The first chart hammers the line with frequent direction changes at 60ms. The second holds nearly flat, then fires massive isolated spikes. The third is just chaos.
Frequent sharp reversals (60ms updates)
Near-flat with massive isolated spikes (120ms)
Rapid zigzag oscillation (50ms)
Real-world data doesn't arrive at regular intervals. WebSocket connections drop, batch updates land all at once, mobile networks stall. This one simulates that: long quiet stretches of 1-3 seconds between points, then sudden bursts at 40-80ms. The tick interval itself is random.
Just a line
Liveline can do a lot. Momentum arrows, particles, orderbooks, scrubbing, time windows. But at the end of the day, if you just want a line that moves when a number changes, it does that just fine too.