VitalsTrend
An SVG trend of one vital sign from a list of Observations, with gaps and a reference band.
Rule it enforces: A missing reading breaks the line into a visible gap; nothing is interpolated across it.
Install
npx shadcn@latest add https://raw.githubusercontent.com/Henrycarto/wardkit/main/public/r/vitals-trend.jsonUsage
import { VitalsTrend } from '@/components/wardkit/vitals-trend';
<VitalsTrend observations={heartRate} maxGapMinutes={120} timeZone="Europe/Vienna" />Examples
Heart rate overnight, maxGapMinutes 120
A failed reading at 22:00 (probe off), a reading entered in error at 02:00, and nothing from 03:00 to 06:00. The line breaks at each; the band is the reference range.
Readings (15)
| Time | Reading |
|---|---|
| 20 Oct 2026, 18:00 | 88 /min |
| 20 Oct 2026, 19:00 | 89 /min |
| 20 Oct 2026, 20:00 | 90 /min |
| 20 Oct 2026, 21:00 | 90 /min |
| 20 Oct 2026, 22:00 | Not measured: Error: Probe off, no valid reading |
| 20 Oct 2026, 23:00 | 90 /min |
| 20 Oct 2026, 23:30 | 104 /min |
| 21 Oct 2026, 00:00 | 112 /min |
| 21 Oct 2026, 00:20 | 118 /min |
| 21 Oct 2026, 01:00 | 124 /min |
| 21 Oct 2026, 01:45 | 116 /min |
| 21 Oct 2026, 02:00 | Entered in error, not shown |
| 21 Oct 2026, 03:00 | 108 /min |
| 21 Oct 2026, 06:00 | 98 /min |
| 21 Oct 2026, 07:00 | 96 /min |
The same readings without maxGapMinutes
Only recorded gaps break the line. 03:00 to 06:00 is joined because nothing says a reading was missed.
Readings (15)
| Time | Reading |
|---|---|
| 20 Oct 2026, 18:00 | 88 /min |
| 20 Oct 2026, 19:00 | 89 /min |
| 20 Oct 2026, 20:00 | 90 /min |
| 20 Oct 2026, 21:00 | 90 /min |
| 20 Oct 2026, 22:00 | Not measured: Error: Probe off, no valid reading |
| 20 Oct 2026, 23:00 | 90 /min |
| 20 Oct 2026, 23:30 | 104 /min |
| 21 Oct 2026, 00:00 | 112 /min |
| 21 Oct 2026, 00:20 | 118 /min |
| 21 Oct 2026, 01:00 | 124 /min |
| 21 Oct 2026, 01:45 | 116 /min |
| 21 Oct 2026, 02:00 | Entered in error, not shown |
| 21 Oct 2026, 03:00 | 108 /min |
| 21 Oct 2026, 06:00 | 98 /min |
| 21 Oct 2026, 07:00 | 96 /min |
Props
| Prop | Type | Description |
|---|---|---|
| observations | Observation[] | Observations of one vital sign, in any order. Required. |
| componentCode? | string | Plot this LOINC component instead of valueQuantity, e.g. "8480-6" for systolic from a 85354-9 panel. |
| maxGapMinutes? | number | Break the line when two readings are further apart than this. |
| title? | string | Chart title. Defaults to the Observation code text. |
| timeZone? | string | IANA time zone for displayed times, e.g. "Europe/Vienna". Defaults to the runtime zone; pass it when rendering on a server. |
| className? | string | Extra classes for the outer element. |
Clinical rules and sources
- A dataAbsentReason, a reading without a value, an entered-in-error reading and a cancelled one each break the line and are marked on the time axis.Source: FHIR R4: DataAbsentReason; FHIR R4: ObservationStatus
- Every point on the line is a recorded value. Values are never interpolated, smoothed or clamped; the axis covers all readings.
- A value with a comparator ("<", ">") is not exact and is shown as a gap, not plotted.Source: FHIR R4: Quantity.comparator
- Readings with different codes or different units are not plotted together and nothing is converted.Source: FHIR R4: Vital signs profiles
- The shaded band is the referenceRange of the most recent reading that has one.Source: FHIR R4: Observation.referenceRange
- A table of every reading and gap sits under the chart for screen readers and for checking.