Side navigation
The panel of destinations, whose counts say what is waiting and whose current item is marked by more than colour.
Overview
A side navigation is a list of places, and the two things it must never get wrong are which one you are on and what is waiting in the ones you are not. Both survive greyscale, because colour is not available to every reader and is the first thing a bad display loses.
How these four sit together — the anatomy of a screen, how deep a hierarchy may go, and what has to happen on a route change — is on Navigation.
Shapes of side navigation
Three are available. Two common ones are not, and the reasons are the same reasons twice.
| Shape | Use | Constraint |
|---|---|---|
| Persistent | The default. Labels always visible, panel always present. | 184–360 px, fixed. It does not resize with the window, because a panel whose width tracks the viewport moves its items under a returning reader. |
| Collapsible | A control in the masthead collapses the panel to reclaim width for content. | Collapsed means gone, not narrowed to icons. Page content reflows; the state is remembered per session and stated in the control. |
| Overlay | Below the breakpoint where a panel and the content both fit. | A real overlay with focus containment and Escape — see Routing. It never covers the alert region. |
An icon-only rail. The common pattern is a 56 px bar of icons whose labels appear on hover or focus. It fails twice here: this system has no icon set, so there is nothing to carry the meaning; and a label available only on hover does not exist on a touch cart, which is half the surfaces this system targets — the rule Tooltip exists to enforce. An unlabelled rail is a navigation the reader must already know.
Navigation that expands on hover. A panel that widens when the pointer enters it changes the page layout without anyone deciding to. The usual mitigations — a delay before expanding, keeping other controls away from the edge — treat the symptom: the design causes accidental expansion and then asks the rest of the screen to make room for it. Expansion is a press, on a labelled control, or it does not happen.
Rules
- Never overlays the alert region. A collapsed side nav expanding pushes content or floats beside it — it never covers the docked alarms.
- Collapsing hides labels, never state. An icon-only rail still shows its counts and its current-item marker.
- Current item is marked by more than colour, and by
aria-current. - Leaving a patient context is explicit. Unsaved work prompts; it is never discarded silently — see Textarea.
- Never auto-navigate. Not on a new alarm, not on a completed background task, not on a colleague's action. Arriving alarms are announced; the clinician moves.
- The back button works. Sections are addressable; navigation state lives in the URL — the same rule as Tabs.
- No more than two levels in the side navigation. Deeper structure is a sign the product needs a different information architecture, not a nested tree.
What the component enforces, and what it only asks for
The rendering rules are held; every fact being rendered is yours.
| Rule | Held by | How |
|---|---|---|
| The current item is marked by more than colour | The component | aria-current="page" plus a fill, a weight and an inset rule.Asserted: SideNav: current matching is segment-wise, not substring |
| A count names what it counts | The component | The count renders through Badge, so a bare number is not expressible. Asserted: SideNav: a count names what it counts |
| Labels are never replaced by icons | The component | There is no icon slot. A 56 px rail whose labels appear on hover cannot be built from this. Asserted: SideNav: an item is a label and a href, with no icon field |
| The current item is the current item | You | currentHref is compared to what you pass. A route change that does not update it leaves the panel confidently marking the wrong place. |
| The counts are correct and current | You | They are numbers you supply, with no clock and no subscription. A section with two unreviewed results and a stale zero reads as empty. |
| It does not overlay the alert region | You | A layout decision — see Grid & layout. |
A rule stated on a component page reads as a rule the component keeps. 3 of these 6 are; 3 are obligations this page was quietly handing to the reader. A navigation panel that marks the wrong item, or shows a stale count, is wrong in exactly the way a reader cannot detect.
Do's and don'ts
Current item marked by fill, weight and an inset rule. Counts show what is waiting inside.
Current item by text colour alone, no counts. Gone in greyscale, and a section with two unreviewed results looks empty.
Labels present, counts on the item that holds them, current marked by more than colour. Readable by a clinician who has never used this product.
A 56 px rail whose labels appear on hover. There is no icon set to carry the meaning and no hover on a cart, so this is a navigation the reader must already know.
Expansion is a press on a labelled control that reports its own state. Nothing changes width because a pointer passed over it.
The rail widens on hover, after 500 ms, and other controls are kept clear of its edge.
The delay and the spacing are treating the symptom. The layout still changes without anyone deciding, and on a cart the pointer gets there by being knocked.
Accessibility
- A
navigationlandmark with a name, because a document with two unnamed navs gives a screen-reader user two identical choices. - The current item carries
aria-current="page", not only a fill. Colour alone fails SC 1.4.1 and fails anyone reading a washed-out cart display. - A count is announced with its noun — “Troponin, 2 results”. A bare number is a quantity with a hidden subject; see Badge.
- Collapsing hides labels, never state. An expand control reports its own state with
aria-expanded.
Outcomes of use
- Effectiveness — the clinician knows where they are and what is waiting elsewhere without opening anything.
- Efficiency — one look answers “is there anything I have not seen?”
- Satisfaction — a panel that does not move under the pointer. Attributes believed to contribute to an outcome; the outcome is settled by observing real use, not by this page — see Usability & context of use.
Clinical safety notes
Trace these in your risk file (ISO 14971) and usability engineering file (IEC 62366-1). Which of these the component keeps and which are yours is stated in the guarantees table; a control the component does not keep is one your risk file must show you keep.
- Never overlays the docked alert region. (you) Mitigates: an alarm covered by a panel expanding.
- Counts are exact and name what they count. (the component names it; exactness is you) Mitigates: a section with two unreviewed results read as empty.
- Labels are never replaced by icons. (the component) Mitigates: a destination identified only by a glyph nobody has been taught — see Perceivable affordance.
Implementation
$ npx shadcn@latest add https://md.notjustany.tech/r/side-nav.json
<SideNav
label="Sections"
items={[
{ label: "ECG", href: "/ecg" },
{ label: "Troponin", href: "/troponin",
count: { value: 2, of: { one: "unreviewed result", other: "unreviewed results" } } },
]}
currentHref={pathname}
/>
Related
- Navigation — depth, grouping and route changes.
- Masthead · Breadcrumb · View heading
- Badge — what a count on an item has to say.