Data grid
The substrate under every list of patients, results and events. Its defaults decide what a clinician sees first and what they never see at all, which makes sorting, filtering and row insertion safety behaviours rather than conveniences.
Overview
A general-purpose data grid optimises for flexibility: sort anything, filter anything, hide anything, remember what the user did last time. Every one of those defaults is wrong in a clinical queue, because each of them can silently remove a patient from view.
This component keeps the flexibility and inverts the defaults. Everything is available; almost nothing is on by default; anything currently hiding data says so on screen.
Complete by default
Opens showing every row in scope. No remembered filter, no collapsed group, no pagination hiding the top of a queue.
Stable under the hand
Rows never move beneath a pointer. Live data arrives staged and announced, inserted when the clinician is ready.
Honest about state
Any non-default sort, active filter or hidden column is stated in the grid's own chrome, legible to someone who did not set it.
Anatomy
| Priority indicator | Finding | ||||
|---|---|---|---|---|---|
| Critical | HARLAND, A. MRN 44 812 907 |
STEMI pattern — anterior V2–V4 | 4 min | 0.94 | |
| Urgent | OKONKWO, B. MRN 51 330 264 |
hs-troponin rising — NSTEMI not excluded | 19 min | 0.71 | |
| Urgent | DELACROIX, M. MRN 47 120 883 |
ST depression — inferior leads | 27 min | 0.66 | |
| Advisory | NAKAMURA, T. MRN 58 904 471 |
Interpretation limited — paced rhythm | 33 min | — |
| Element | Rule |
|---|---|
| State bar | Row count and the active sort, always. This is the component's honesty surface. |
| Sticky header | Column meaning stays visible while scrolling. Never sticky at 200 % zoom, where it would consume the viewport. |
| Sortable header | A real <button> inside the <th>, with
aria-sort on the header. |
| Priority rail | 4 px of IEC hue where rows carry clinical priority. Always paired with a text column. |
| Selection | --accent fill plus aria-selected. Never colour alone. |
Sorting
The default sort is a product decision with clinical consequence, declared once and enforced by the component. In AcuteLine it is priority, then longest waiting — see Triage worklist.
- Default sort is restored on every session start. A sort order inherited from the previous shift is a hazard, so it is never persisted.
- A non-default sort is stated in the bar, in words, persistently — a clinician walking up to a colleague's screen must be able to see the queue is not in priority order.
- One-click reset is always present while a non-default sort is active.
- Sorting never reorders rows under the pointer. It is an explicit action, so the reorder is expected — but focus stays on the header that was activated.
| Priority indicator | |||
|---|---|---|---|
| HARLAND, A. | 0.94 | 4 min | |
| VASQUEZ, R. | 0.88 | 41 min | |
| OKONKWO, B. | 0.71 | 19 min |
A reviewed 41-minute case now sits above an urgent 19-minute one. The bar says so in words, not with a small arrow icon.
Live data
New rows are staged, not inserted. A banner announces how many are waiting and the clinician inserts them. Auto-insertion is permitted only after a documented idle interval with no pointer or keyboard activity — because a queue that reorders while someone is reaching for row two produces wrong-patient selection.
| Priority indicator | Patient | Finding | Waiting |
|---|---|---|---|
| HARLAND, A. | STEMI pattern — anterior V2–V4 | 4 min | |
| OKONKWO, B. | hs-troponin rising | 19 min |
Once inserted, new rows carry a temporary marker so a clinician can see what changed since they last looked. The marker is positional and textual, never a colour wash that could be mistaken for priority.
Filtering and columns
- No filter is active on open. Filters are never persisted across sessions.
- Hidden row count is always shown next to the filter control —
Showing 5 of 23 · 18 hidden by filter. - Critical rows bypass filters entirely and render with a note that they fall outside the current filter.
- Hidden columns are disclosed in the state bar. A column a clinician does not know is missing is worse than one they chose to hide.
- Export and print always contain every row in scope, filtered or not, with the filter stated on the output.
Density
| Density | Row height | Use | Touch |
|---|---|---|---|
comfortable | ~44 px | Default. Any grid whose rows are tappable. | Yes |
default | ~36 px | Workstation review with pointer input. | No |
compact | ~28 px | Dense analytic review, pointer only, non-clinical-priority data. | No |
Density never removes information — it reduces padding only. A compact grid
that drops the MRN is not compact, it is incomplete. On touch platforms the grid is locked to
comfortable and the row must satisfy the physical touch floor; see
Scaling.
Empty, loading and degraded
Empty and demonstrably alive. The timestamp and source count are the proof.
Indistinguishable from a dead ingestion pipeline. An empty clinical queue must always prove why it is empty.
- Loading shows skeleton rows, never a blank grid, so the clinician knows data is coming rather than absent.
- A partial load is an error, not a shorter list. If some rows failed to fetch, the grid says so at Urgent — silent partial coverage is the most dangerous state a clinical queue can be in.
- Stale data is labelled with its age once beyond a documented threshold.
Do's and don'ts
New rows staged and announced. The clinician inserts them when their hand is not already moving.
| NEW — inserted at top just now | |
| OKONKWO, B. |
Live insertion at the top. Everything below moves down mid-reach, and the wrong patient opens.
The state bar states the sort in words, permanently, for whoever walks up to the screen.
A small arrow in a header. A colleague cannot tell this queue is sorted by confidence, not acuity.
Accessibility
- A real
<table>with<th scope="col">. Div-based grids lose screen-reader table navigation entirely. aria-sorton the sorted header, and sorting via a real button so it is keyboard-reachable and announced.- DOM order equals visual order. Never reorder with CSS — announced order must match seen order.
- Selection uses
aria-selectedand a non-colour cue. - Live updates announce through one polite summary region — "2 new cases" — never by making rows live regions.
- Sticky headers are disabled at high zoom and the grid reflows rather than scrolling horizontally at 320 px.
- Row actions are reachable by keyboard without entering a roving-tabindex maze; the primary action is a link or button in the last cell.
Outcomes of use
What this contributes to, in the terms of Usability & context of use. These are attributes believed to contribute to an outcome; the outcome itself is settled by observing real use in a specified context, not by this page.
- Effectiveness — Completeness dominates here: every case in scope is visible. Accuracy is secondary but real — the row opened is the row intended.
- Efficiency — Time to locate the highest-acuity case, and the number of scrolls, sorts and filters needed to get there.
- Satisfaction — Trust that the queue is complete and genuinely in the order it claims. A single discovered omission costs more than a hundred correct renders earn.
Clinical safety notes
Trace these in your risk file (ISO 14971) and usability engineering file (IEC 62366-1).
- No persisted sort or filter. Mitigates: a clinician inheriting a colleague's view and believing it is the default.
- Non-default state stated in words. Mitigates: a re-sorted queue being read as clinical priority order.
- Criticals bypass filters. Mitigates: the highest-acuity patient being filtered out of view.
- Rows staged, never inserted under the pointer. Mitigates: wrong-patient selection from a queue reordering mid-reach.
- Density never removes information. Mitigates: identifiers dropped to fit more rows.
- Partial load raises an alarm. Mitigates: silent incomplete coverage.
- Export contains everything in scope. Mitigates: a handover or audit document that silently inherits a filter.
- Empty state proves liveness. Mitigates: a failed pipeline reading as a quiet ward.
Implementation
$ npx shadcn@latest add https://md.notjustany.tech/r/data-grid.json
// TanStack Table under a shadcn-styled shell. The clinical defaults are
// baked into the component, not left to each call site.
<DataGrid
columns={columns}
data={cases}
// declared once; restored on every session start
defaultSort={[{ id: "priority", desc: true },
{ id: "waiting", desc: true }]}
persistState={false} // governed: never true in a clinical queue
stageIncoming // new rows announced, inserted on request
autoInsertAfterIdle="PT30S"
bypassFilter={(row) => row.priority === "critical"}
density="comfortable"
onRowActivate={(row) => openEvidence(row.id)}
/>
| Prop | Type | Default | Notes |
|---|---|---|---|
defaultSort | SortingState | — | Required. Restored on every session start. |
persistState | boolean | false |
Setting true on a clinical queue fails lint. |
stageIncoming | boolean | true |
Staged arrivals. Disable only for non-clinical grids. |
bypassFilter | (row) => boolean | — | Rows that render regardless of active filters. |
density | comfortable | default | compact |
comfortable | Locked to comfortable on touch. |
onRowActivate | (row) => void | — | Must open evidence, never an irreversible action. |
Related
- Triage worklist — this component's principal use.
- Alert Banner — the compact variant used inside rows.
- Scaling & displays — row height on touch.
- Typography — tabular numerals in columns.