Accordion & layout panel
Two containers with opposite promises. A layout panel says this is here, and it stays here. An accordion says this is here, but you will have to ask for it. In clinical software the second promise is expensive, so it is made rarely and never about anything that changes a decision.
Overview
Both exist to give a screen structure the clinician can rely on. The panel does it by drawing a boundary — everything inside belongs to one subject, and the panel header says which. The accordion does it by trading visibility for density.
That trade is the whole risk. Collapsed content is content nobody read. Not "content read later" — in practice, not read at all. So the question this page keeps asking is the same one: is it acceptable for a clinician to never open this? If the answer is no, it does not go in an accordion.
The boundary is the meaning
A panel's border is a claim about what belongs together. Nothing inside a patient panel belongs to another patient.
Collapsed means optional
Only content a clinician can safely never open is eligible for an accordion. Everything else is on screen.
The header carries the state
A closed section still reports what is inside it — the count, and any priority that would change what happens next.
Layout panel
| Part | Rule |
|---|---|
| Header | Required. Names the content. On patient-scoped panels it also names the patient — see Patient header. |
| Subject | One panel, one subject. A panel that mixes two patients' data has no valid header, and that is the signal it is wrong. |
| Border | 1 px at ≥ 3:1 against both adjoining surfaces (WCAG 2.2 SC 1.4.11). The boundary is load-bearing, so it must actually be visible. |
| Body | Scrolls internally only when the panel has a fixed height and its header stays put. Never a scroll region with no visible edge. |
| Toolbar | Optional, inside the border — that is what makes its scope unambiguous. See Toolbar. |
| Elevation | Panels sit flat. Elevation is reserved for things that overlay — see Elevation. |
Accordion
ST elevation 3.1 mm in V2–V4 measured at J+60 ms. Reciprocal depression in II, III, aVF.
AcuteLine model v4.2 · analysed 15:41:04 · decision support only
The third section is closed but declares that something advisory is inside it — a lead was reversed. A clinician who never opens it still knows to.
An accordion may never be the only route to an alarm, an active finding, an unacknowledged item, or anything the clinician must weigh before committing. Those are rendered expanded, or not in an accordion at all. If a section header would ever need to say "critical", the content behind it should not have been collapsed.
- Never exclusive-open by default. Opening one section must not close another; comparing two sections is a normal thing to want.
- Never collapse what the clinician opened, on refresh, on data arrival, or when a sibling opens.
- Never nest accordions. Two levels of collapse is two chances to lose something, and the second level is almost never opened.
- Never put a form across sections. Validation errors in a collapsed section are errors the clinician cannot see; if a form is long, use Tabs with a visible progress statement or a single scrolling panel.
- Never animate longer than 150 ms, and honour
prefers-reduced-motion— see Motion. - Headers are real headings containing a button, so the section structure is navigable by heading, not just by tab.
States
| State | Rendering |
|---|---|
| Collapsed | Header, count, and highest contained priority. aria-expanded="false". The
caret is decorative; the state is in the attribute. |
| Expanded | Region labelled by its header button, so its boundary is announced. |
| Loading a section | Content loads on expand; the header shows activity and the section keeps its height stable enough not to throw the page. |
| Section empty | Expands to a stated reason, never to nothing. See Empty state. |
| Section unavailable | Header stays, marked unavailable with a reason. Sections never disappear — a missing section is indistinguishable from a section that never existed. |
| Panel degraded | The panel renders with its header and an explicit statement of what is missing. It does not collapse to nothing. |
Do's and don'ts
Closed, but the header declares what is inside. Collapse costs a click, not a finding.
A vague label hiding an advisory. Nothing on screen gives the clinician a reason to open it.
An active finding is rendered, not collapsed. Nothing that changes the next action lives behind a caret.
A critical alarm collapsed behind a count. The badge is red and still nobody opens it during a busy shift.
One panel, one subject, named in the header. The boundary is a claim the header can back up.
Three patients' trajectories inside one border, under a header that names none of them.
Two sections open at once. Comparing detail against priors is the reason both exist.
Exclusive-open. Opening the priors closed the analysis the clinician was reading, and they have to remember what it said.
Accessibility
- Header is
<h3>wrapping a<button>, at the right level for its place in the page outline. Screen-reader users navigate by heading far more than by tab. aria-expandedon the button,aria-controlsto the region, and the region labelled back witharia-labelledby.- The caret is
aria-hidden. A glyph is not a state. - Contained state is in the accessible name — "Device and acquisition notes, 1 advisory, collapsed" — not only in a coloured chip.
- Panels use
<section aria-labelledby>so the boundary the border draws visually exists non-visually too. - Panel borders meet 3:1 against both surfaces they separate (SC 1.4.11). This is verified in CI against tokens.
- Expansion animates ≤ 150 ms and respects
prefers-reduced-motion, which disables the height transition entirely. - No scroll trapping. An expanded section extends the page; it does not create an inner scroll area the user cannot escape by keyboard.
- Targets ≥ 24 px — the whole header row is the target, not just the caret (SC 2.5.8).
- Find-in-page. Where the platform supports it, collapsed content uses
hidden="until-found"so browser search still reaches it. Nothing safety-relevant depends on that support existing.
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 — nothing decision-relevant is behind a caret, and closed sections still declare what they hold.
- Efficiency — a screen dense enough to work in, without the cost of opening three sections to answer one question.
- Satisfaction — sections that stay as they were left. Re-collapsing what someone opened is a small betrayal, repeated all shift.
Clinical safety notes
Trace these in your risk file (ISO 14971) and usability engineering file (IEC 62366-1).
- Alarms and active findings are never collapsible. Mitigates: a critical finding never read because it was behind a caret.
- Closed headers declare contained count and priority. Mitigates: no cue to open the section that matters.
- Non-exclusive expansion. Mitigates: losing the section being read when another is opened.
- Expansion state preserved across data arrival and refresh. Mitigates: re-reading work already done, and missing that content changed.
- No forms split across collapsed sections. Mitigates: validation errors hidden from the person being asked to fix them.
- One panel, one subject, named in the header. Mitigates: two patients' data read as one patient's.
- Panels degrade with a stated reason rather than disappearing. Mitigates: a missing panel read as absent data rather than a failure.
- Sections never removed silently. Mitigates: a permission or availability state indistinguishable from non-existence.
Implementation
$ npx shadcn@latest add https://md.notjustany.tech/r/accordion.json
$ npx shadcn@latest add https://md.notjustany.tech/r/layout-panel.json
// A collapsible section must be able to describe itself while closed,
// so summary is required — not optional metadata.
<Accordion multiple defaultOpen={["detail"]}>
<Accordion.Section
id="device"
heading="Device & acquisition notes"
headingLevel={3}
summary={{ count: 2, highest: "advisory" }}
>
…
</Accordion.Section>
</Accordion>
// Anything at advisory or above cannot be the sole occupant of a
// collapsed section — the component refuses to render it closed.
if (summary?.highest && PRIORITY_ORDER[summary.highest] >= PRIORITY_ORDER.advisory
&& summary.unacknowledged) {
throw new Error("[Accordion] unacknowledged items at advisory+ cannot be collapsed.")
}
// A panel's header is required, and patient-scoped panels must name
// the subject they belong to.
<LayoutPanel heading="Serial troponin" subjectId="MRN-55-1120">…</LayoutPanel>
| Prop | Type | Notes |
|---|---|---|
multiple | boolean |
Defaults to true. Exclusive-open must be opted into and is discouraged. |
heading · headingLevel | string ·
2|3|4 |
Required. Renders a real heading, not a styled div. |
summary | SectionSummary |
Required. Count plus highest contained priority; used in the closed header and the accessible name. |
LayoutPanel heading | string |
Required. No unlabelled panels. |
subjectId | string |
Required on patient-scoped panels. Renders the subject in the header and guards against mixed-subject content. |
Related
- Card — when the container is a discrete object rather than a region of a layout.
- Tabs — mutually exclusive views, where an accordion would be wrong.
- List & tree — the same collapse rule applied to hierarchy.
- Toolbar — what may sit inside a panel's border, and why.
- Elevation — why panels stay flat.
- Motion — the 150 ms ceiling and reduced motion.