Live vitals that read like a bedside monitor: metric tabs whose values roll, and a line that scrolls continuously over the usual range. Built on Number Roll, the chart palette and realtime-core's ticker.
When to use
- Remote monitoring or a wearable companion: heart rate, oxygen and breathing arriving every second or so.
- When a value leaving its usual range should be seen at a glance, without an alarm.
- When the last minute matters more than the last month; for long trends use a Line Chart.
When not to use
- Historical trends over days or weeks: use Line Chart or Metrics Dashboard.
- A single static reading: use Stat Tile.
- Clinical alarms. This block informs; it does not alert, escalate or diagnose.
Installation
npx shadcn add @eased-pro/vitals-cardNeeds the @eased-pro registry and your Pro token in components.json. Create a token and copy the setup from your account .
Usage
import { useMemo, useRef } from "react";import { createTickStream, useTicker } from "@/components/eased/_primitives/realtime-core/realtime-core";import { VitalsCard } from "@/components/eased/vitals-card/vitals-card";export function Monitor() { const ref = useRef<HTMLDivElement>(null); const stream = useMemo(() => createTickStream({ seed: "vitals", series: [{ id: "hr", initial: 72, kind: "level", decimals: 0 }] }), []); const live = useTicker(stream, { target: ref, history: 61 }); return ( <div ref={ref} className="flex h-96 flex-col"> <VitalsCard className="flex-1" tick={live.index} paused={!live.running} metrics={[{ id: "hr", label: "Heart rate", unit: "bpm", values: live.series("hr"), range: { low: 50, high: 100 }, domain: [40, 130], pulse: true }]} /> </div> );}Pass one more sample than the window you want to show: the extra one waits past the edge and slides in.
API reference
VitalsCard
| Prop | Type | Default | Description |
|---|---|---|---|
metrics | VitalMetric[] | { id, label, unit, values, range, domain?, decimals?, color?, pulse? }. values are evenly spaced, oldest first. | |
tick | number | Increments with each new sample; each change scrolls the line one step. | |
intervalMs | number | 1000 | Time between samples; one scroll step lasts this long. |
paused | boolean | false | Stops the scroll and the heartbeat. Pass !ticker.running. |
value / defaultValue / onValueChange | string | first metric | The selected metric. |
windowLabel | string | "Last 60 seconds" | Above the chart and in its accessible name. |
locale | string | "en-US" | |
className | string | "" | Appended to the root. Give it a height; the chart fills it. |
Keyboard
| Key | Action |
|---|---|
Tab | Moves to the selected metric tab. |
← / →, Home / End | Move between metrics and select one. |
Accessibility
- The metrics are a
tablist; the chart is thetabpanel, an image named with the current value and the usual range. - Out-of-range values are marked by colour and by the flag's text, never colour alone.
- The heartbeat and the scroll stop off screen, while paused, and under reduced motion.
Motion
The line is redrawn once per sample and slid one step left over the sample interval with a linear tween, so it moves continuously rather than in jumps. Values roll on the snappy spring. Leaving the range tints the stretch outside the band, the newest dot and the value in warning over 200ms, and the flag pops in from 85%. The heart beats at the live rate. Reduced motion: no scroll or heartbeat; values change at once.
Notes for AI
Pick this for live, per-second vitals with a usual range. Feed it from useTicker in realtime-core (deterministic, pauses off screen) and pass tick={ticker.index} and paused={!ticker.running}. Set domain wider than range so excursions have room, and pulse only on heart rate.
Don't animate the samples yourself or remount on each tick; the block scrolls from a motion value. Don't use danger colours or alarm copy for out-of-range values, and don't present the readout as a diagnosis.
Related
- Lab Results: reference ranges for slower, discrete results.
- Sparkline: a word-sized trend.