Cadence Clinical
Patterns

Observation table

Shows observations over time as a flowsheet, with the band each value was given, its change since the value before, and a total for each round.

Installation

The clinical components are not on npm yet. Once they are:

import { ObservationTable } from "@cadence-clinical/clinical";

Import its stylesheet after the ui stylesheet, so Tailwind finds its classes:

@import "@cadence-clinical/ui/styles.css";
@import "@cadence-clinical/clinical/styles.css";

From the shadcn registry:

pnpm dlx shadcn@latest add @cadence/observation-table

The registry installs it beside your Table, Tooltip, Dropdown menu and Button, and adds @cadence-clinical/core.

Usage

From FHIR, read the Observations into series, apply your schema, and add up the totals:

import { ObservationTable } from "@cadence-clinical/clinical";
import { applyObservationSchema, groupRounds, scoreRounds } from "@cadence-clinical/core";
import { observationSeries } from "@cadence-clinical/fhir";

// schema: your observation schema, or one from a Region package.
const { series, issues } = observationSeries(bundle, { series: schema.series });
const vitals = applyObservationSchema(series, schema);
const totals = {
  label: schema.total.label,
  rounds: scoreRounds(groupRounds(vitals, 5 * 60_000), schema),
};

<ObservationTable
  label="Vital signs"
  series={vitals}
  totals={totals}
  now={now}
  timeZone="Australia/Melbourne"
/>;

observationSeries checks every field it reads. Anything it cannot read is in issues, with the resource and the reason.

API

The Observation table is a clinical pattern built on Table, Tooltip, Dropdown menu and Button. shadcn has no equivalent. It renders a div around the table, and spreads the rest of its props onto it.

Props

PropTypeDefault
seriesInterpretedSeries[]Required
labelstringRequired
nowstring | numberRequired
timeZonestringRequired
totals{ label: string; rounds: RoundTotal[] }None
localestring"en-AU"
hourCycle"h23" | "h12""h23"
roundWindowMsnumber300000
defaultHiddenRowsstring[][]
onHiddenRowsChange(keys: string[]) => voidNone
messagesPartial<ObservationTableMessages>en-AU

messages replaces any of the words the table shows or reads out. TOTAL_ROW is the key of the total's row, for defaultHiddenRows.

Short names

A series definition's shortLabel, such as "RR", names its row when labelStyle is "short", the default. A series with no short name is named in full. labelFor(definition, style) in @cadence-clinical/core gives the same name, for your own components.

Totals

Group the rounds for scoreRounds with the same window as the table's roundWindowMs. A total whose round is not one of the table's columns throws, because it would otherwise be shown nowhere.

In a round where one row has two values, scoreRounds counts the latest. When the latest cannot be scored, such as a value in another unit, the row is missing and the total is Incomplete: an earlier value is not used in its place. A value at a level that escalates raises the round whichever value it is.

Times

now and timeZone are required. The table never reads the clock or the device's time zone, so a page rendered on a server shows the same days and times as the browser. Days near now are "Today" and "Yesterday", earlier ones a short date, and times use the 24 hour clock unless hourCycle is "h12".

Accessibility

  • The table is named by label, and so is the region that scrolls it. The region takes focus, so a keyboard can scroll it.
  • Each value's band, change, exact time and the source's interpretation are in a visually hidden span, so a screen reader reads them with the value. The tooltip shows the same to sighted readers.
  • The values are one tab stop. The arrow keys, Home and End move between them, and each opens its tooltip when it takes focus.
  • Row headings are th elements with scope="row", and each round's time is a time element with the full date and time for a screen reader.

On this page