← Portfolio

OxiWear · 2026 · Senior UX/Product Designer

OxiWear Clinical Report Portal

A restructure of the portal used to generate FDA-regulated medical records. On this surface, a confusing interface isn't just friction — a misconfigured report can put outdated vitals in front of a clinician.

The work below splits into two kinds of issue, found and fixed differently: problems, friction that never touched clinical data, and hazards, a plausible path to a clinician acting on wrong or missing data under ISO 14971 / IEC 62366-1.

FDA-regulated design systemReport workflow redesignHazard-control validationCross-functional alignment

01Overview

Duration — Jul–Sep 2026Stakeholders — Head of Product, Clinical, Sales, CS, Engineering
Delivered
  • 3 problems solved — the IA and terminology restructure, the Verify Data check, and the vitals selection
  • 6 hazards controlled under ISO 14971 — 3 found through real incidents, 3 designed out from the start
  • The Figma-and-Notion design system behind them

OxiWear's Clinical Report Portal is the web application care specialists use to monitor home-based device users wearing OxiWear's FDA-regulated ear-worn pulse oximeter, and to generate the clinical reports (General, Sleep, 6MWT) that go into a patient's medical record. It spans two surfaces: a fitness dashboard for lower-stakes personal tracking, and the medical dashboard this case study focuses on.

Three threads ran through the work: solving usability problems through an IA restructure, controlling six clinical-safety hazards under ISO 14971, and formalizing those hazard controls into a Figma-and-Notion design system now used by design and engineering beyond this project.

The guiding principle throughout: safety and reliability first, ease of use second, aesthetics last. Every trade-off in this case study — which UI pattern shipped, which control got added — was decided by that ordering, not the reverse. The side panel that shipped for vital selection isn't the visually tidier option — a dropdown was — but it preserved full data resolution. The Verify Data check isn't a flashy feature — it exists because a wrong report is worse than an ugly form.

02Design process

1

Sort every issue into problem or hazard first, before designing anything.

2

For a hazard, trace the chain: use error → hazardous situation → harm → control. (.)

3

Check the design system before inventing a new fix. Most of the hazards below already had a control — the job was recognizing it applied here.

4

Check whether the fix itself introduces a new hazard. The auto-save/restore control below is the clearest example: its first version created a new risk, caught before shipping.

5

Validate at the right level — problems against the people who reported them, hazards against the design-system baseline they came from.

03Key decision: staging the rollout around regulatory risk

OxiWear's portal spans two surfaces: a fitness dashboard and a medical dashboard used for clinical reporting. The medical side carries real regulatory weight — a client complaint there gets formally logged and can trigger a compliance review. So I proposed piloting the redesign on the fitness dashboard first — lower stakes, faster feedback — before extending it to the medical dashboard, which adds the Sleep and 6MWT report types.

Problems

Usability friction with no plausible path to clinical harm — it costs time or a support ticket, but no incorrect, missing, or delayed clinical data ever reaches a clinician. Fixed through IA and terminology redesign.

Hazards

A plausible path from a use error to a care specialist acting on wrong, missing, or delayed clinical data, under ISO 14971:2019 / IEC 62366-1. Fixed through hazard-specific controls.

04Problems & fixes

Validated with the people who reported them

01 — Selecting vitals for the line graphA dropdown-vs-panel disagreement, resolved by asking whether the step was diagnosis or setup — which led to removing the report's hard data cap entirely.

Two ways to select vitals

V1 of the reporting flow was rigid: reports defaulted to just SpO₂ and pulse rate, capped at a hard ten-minute window of raw, one-second-resolution data. As we expanded the flow to let clinicians build custom multi-vital reports, I hit a decision point with my Head of Product at report-configuration step two: keep the portal's established grouped-dropdown pattern for selecting vitals, or adopt a side panel — every vital as a checkbox, with live min/avg/max previews.

My first instinct

I initially resisted the side panel — its own width would eat further into an already tight ten-minute window. But the design principle forced a sharper question than which one looked better: was this step a diagnosis stage or a setup stage?

The question that settled it

It's setup — an administrative step where the chart is a sanity check, confirming which streams are active and roughly what they look like, not something a clinician is diagnosing off of. Shrinking that preview cost nothing in safety. With safety off the table, ease of use won: for someone toggling through dozens of raw physiological streams, the side panel's multi-select and live summaries were a clear improvement over reopening a dropdown for every vital.

1a — Grouped dropdownSelect vitals ▾SPO₂ · PULSE RATE≈ 10 min windowClosed dropdown holds no persistent space — the graph gets thefull width. But checking or changing what's selected meansreopening the dropdown.1b — Side panelSHIPPEDSpO₂929699Pulse rate607490Resp. rate121620VO2384246PI81216HRV284562Temp979899SPO₂ · PULSE RATE≈ 6.5 min windowPanel costs ~3.5 min of width — every vital stays visible, nodropdown round-trip, and scrolling recovers full resolutionover time.

The bigger ceiling

That still left the real ceiling unsolved — a hard ten-minute cap on the data itself. Rather than accept it as fixed, I worked with engineering to remove it: the side panel is strictly a step-two setup tool and disappears once the report generates, and the final report viewer no longer compresses data into a fixed window at all — it expands horizontally as a scrollable timeline, preserving full one-second resolution no matter how many vitals or hours a clinician selects.

Where the trade-off still shows up

That full resolution doesn't carry over to a printed report, though — paper can't scroll. A print shows the overall trend rather than every data point past a certain range. That's an accepted limitation of the static medium, not a gap in the interactive design.

02 — Consolidating navigationFour scattered tabs became three task-based hubs, so a feature carries through the whole task instead of stopping halfway.

How it surfaced

Customer Success flagged a recurring cluster of tickets, and Head of Product pulled together CS, Sales, me, and engineering — some of it looked like it could be bugs, not UX. The pattern traced back to quick actions that didn't cover what people needed next: someone would invite a user, then not realize managing them meant leaving that flow for the main interface — and stay on the invite screen asking how to delete a user instead. Separately, “Invite patient” and “Invite dashboard user” were indistinguishable until clicked.

Why patching wasn't enough

I first tried patching each quick action individually, but there were too many gaps to patch one by one, it didn't resolve the icon confusion, and it kept every major action routed through the dashboard — diluting its actual purpose as an at-a-glance monitoring view. That's what pushed this to a full IA restructure, confirmed by mapping the real user journeys, walking the flows myself, and running three internal moderated sessions.

The restructure

Four tabs became three task-shaped hubs — Dashboard, Reports, Setup — so a feature carries through the full task instead of stopping at the first step. Ambiguous naming was standardized: “Device user” instead of hardware names, “Care specialist” as a defined staff role, “Teams” for device-user groups.

Before — 4 tabs, plus admin management buried in the profile menu

  • OxiWear portal
    • Main navigation4 tabs
      • Dashboardmonitoring, plus report and invite actions bolted on
        • General report — downloadbuttonone-time download, no history — the one report with no home
        • 6MWT reportbuttonduplicated — also lives in the Reports tab
        • Invite device user · Invite care specialisticonsduplicated in Devices / Admin, and unlabelled here
      • Reports6MWT and Sleep reports — but not General
      • Devicesmisnamed — this is the list of device users
      • Admingroups device users — but a second “Admin” lives in the profile menu
    • Profile menu › Admin managementtop-rightthe second “Admin” — buried among account settings, manages staff roles

duplicated, buried, or mis-namedReport and invite actions bolted onto the Dashboard as unlabelled icons · the list of device users called “Devices” · two different things both called “Admin,” one of them hidden in the profile menu.

03 — Verify Data check, before configuration opensA too-lenient pass/fail check let sparse data through unnoticed — replaced with real per-vital data counts, shown before configuration opens.

The pattern behind the escalations

A specialist would select a time range — an afternoon window, say — expecting it fully populated with one-second data, and the generated report would come back with noticeably fewer points than expected, or visible gaps in the line graph. Real device gaps cause this: the device wasn't worn continuously, or a Bluetooth connection dropped. But to the specialist it looked broken, and they'd contact Customer Success, who'd loop in engineering to check the backend — confirm the device user, confirm the timeframe — before concluding it really was just a gap in wear time. That round-trip cost time for the specialist, CS, and engineering, every time it happened.

A modal with its own problems

The modal it happened in had its own separate problems. It was a blocking popup — a specialist couldn't step away mid-task and come back to where they left off — and content-heavy interactions, editing the graph especially, ran into real technical constraints trying to fit inside it. A full-page split layout replaced it: room enough for that interaction itself, and room enough to add a real diagnostic step alongside it.

Where the first fix fell short

The first version of that diagnostic step verified against a low bar: if a single valid data point existed anywhere in the selected range, it passed. That caught the worst case — a genuinely empty range — but not the more common one: a range that was mostly gaps, verified successfully, and then generated a report with far less data than the specialist expected.

Showing the real coverage

The version that shipped replaced pass/fail with the actual data-point count per reading type, shown before configuration opens — so a specialist can see real coverage for themselves, and tell a legitimate gap from a configuration mistake, without escalating to find out.

Before

Before: after Verify Data runs, a small message reads Data verified for the selected range, with Edit and Next buttons below. The rest of the screen is blurred.

After

The Create General Report screen after Verify Data has run. Report Specifications on the left: device user, report option CSV, and a start and end timeframe in EST. On the right, Data Integrity and Availability lists four passed checks — device user detected, active timeframes validated (96h 46min 23s continuous), SpO2 readings verified (333,943 data points), pulse rate readings verified (233,333 data points) — and ends with a Check Complete message. Re-verify data and Next: Preview and Generate buttons sit at the bottom.

05Hazards & controls

Validated against a hazard-matrix review

  1. Step 1

    Use error

  2. Step 2

    Hazardous situation

  3. Step 3

    Harm

  4. Step 4

    Control

Every control gets run back through this same chain — checked for a new use error, hazard, or hazardous situation of its own.

A hazard is different from a problem: there's a plausible path from a use error to a hazardous situation to actual clinical harm, formally assessed through a hazard matrix — severity plotted against probability, scored against a threshold that decides whether a control is required. (See for the full chain and how that scoring works.)

All six were already working as fixes on the fitness dashboard before this project — three found through a real incident, three designed out from the start — but only as one-off screen decisions. I turned them into a documented Figma library and Notion reference, drawing on FDA design-control experience from earlier projects, then confirmed each still held up in medical's higher-stakes context. That library is now what design and engineering both reference for these patterns.

Found through a real incident

01 — Interruption mid-reportA live-monitoring alert would pull a specialist away — and the fix for that introduced a second, subtler hazard of its own.

The original hazard

A care specialist creating a report would get pulled away by a live-monitoring alert on another client — meaning a choice between finishing the report or losing everything entered.

First fix — and the hazard it introduced

A saved-draft pattern was the obvious fix, but a full report takes one to three minutes; re-verifying a draft could take longer than restarting. So the report instead restored the specialist's exact state on return — but that fix introduced its own hazard: restoring directly into step two meant losing the visual confirmation of which device user was even being configured for, risking a report generated for the wrong patient.

Corrected control

Returning from an interruption always lands back on step one first — the confirmed device user — before the specialist clicks through to step two, where everything already entered is preserved.

beforeStep 1 — Confirm device user and time rangeStep 2 — Configuration (resumes here)
afterStep 1 — Confirm device user and time range (resumes here)Step 2 — Configuration (entries preserved)
02 — Timezone confusionA clinician converted a report’s date range to his own timezone — and pulled data for the wrong window.

A clinician on the East Coast needed a report for a patient in San Francisco, converted the date range to his own timezone before entering it, and pulled the wrong window — the system had been displaying the device's local time all along, not his.

The Create General Report screen, Step 1: Verify Data. The timeframe section is titled Timeframe Configuration (EST) and has a note beside it reading All time entries are recorded in EST (UTC-5), above the Start and End date and time fields. The other fields are blurred.

Control: timestamps are always labeled with the device's own timezone (e.g., “14:32:05 PST”) — nothing to convert.

03 — Colour collapse on a printed reportTwo vitals’ lines passed every colour test on screen, then looked identical on paper.

A clinician printed a report and said two vitals' lines looked identical. The colours had already passed WCAG contrast and a colourblind-safe palette test — but a monochrome printout strips hue entirely and keeps only luminance, so two colourblind-distinguishable colours can still collapse into the same grey.

On screenPrinted — same grayPattern + shapePasses WCAG + colorblind testHue gone — luminancenearly identicalSolid + circle vs.dashed + triangleprints asfixTwo vitals that read as distinct colors on screen collapse tonearly the same gray once printed — pattern and node shape keep them apart either way.

Control: pattern and node shape are the primary differentiator, not colour, with an always-visible legend.

Designed out from the start — no incident behind these

  • Lost reports with no system record→ Retained report history
  • Alarm fatigue from decorative red/amber→ Red/amber reserved for abnormal vitals only
  • Accidental medical record deletion→ Multi-step dialogs requiring value matching

All six, at a glance

Hazard

Remote clinicians converting to their own timezone.

Timestamps always show the device’s own timezone.

Colours collapsing to the same grey on a monochrome printout.

Pattern + shape as primary differentiator; always-visible legend.

A live-monitoring emergency forces a choice mid-report.

State restores via the device-user confirmation step first.

Lost reports with no system record.

Retained report history.

Alarm fatigue from decorative red/amber.

Red/amber reserved for abnormal vitals only.

Accidental medical record deletion.

Multi-step dialogs requiring value matching.

06The design system

Why a system, not just screens: the same low-level questions — how to show a number that updates, a timestamp, a two-line chart so it can't mislead a care specialist — kept coming back on a regulated product. I answered them once, in a written spec the whole web product now builds against: the FDA-Regulated Design System & Clinical UX Guidelines. It's the mobile app's already-validated human-factors work translated into web components.

What makes it defensible on a regulated product: every rule names the hazard it prevents, so any component traces back to a specific risk — the six hazards mapped above.

Numbers

Tabular figures on every live value; slashed zero on device IDs

Prevents: a shifting column, or an O / 0 mix-up, causing a misread

Time

Absolute 24-hour time with a zone (14:32:05 EST) — never “5 min ago”

Prevents: a relative timestamp misread on a cross-shift handover

Colour

Red and amber only on abnormal vitals; teal is the one interactive colour

Prevents: alarm fatigue — a real warning lost among decorative colour

Charts

Series told apart by pattern + node shape, not colour; legend always visible

Prevents: two vitals collapsing into one on a black-and-white printout

Destructive actions

Multi-step dialog with value matching, not a plain “Confirm”

Prevents: an irreversible medical record deleted by a mis-tap

Alerts & offline

Three-tier alert priority; a persistent offline indicator with sync status

Prevents: a low notice reading as urgent, or stale data read as live

Icon buttons

0 ms action-verb tooltip, 44 × 44 px hit area, matching ARIA label

Prevents: a mystery-meat icon, or a hurried tap hitting the wrong action

From the Figma library

The rules ship as real styles and tokens. The type scale forces tabular figures on every vital and a slashed zero on device IDs; the colour system keeps red and amber off the interface chrome and reserves them for clinical state.

The medical type scale — seven Inter styles from Vital / Value (32px semibold, tabular figures) down to Data / Timestamp (12px, tabular), each with its spec and usage note.
Type scale — tabular figures on every vital and data column.
The colour token sheet — Primary & Brand (OxiWear teal, purple accent), Text ramp, Disabled, Error (Danger red, Warning amber), and Border & Background tokens.
Colour tokens — teal for interaction; red and amber held back for clinical state.

Charts, built for monochrome

Reports get printed in black and white. Pattern and node shape — not colour — keep two series apart, and the legend never hides.

SpO₂ (%)Pulse rate (bpm)Legend always visible · x-axis in absolute time
70809010014:0014:2014:4015:00

Desaturate it and it stays completely readable — that's the test.

07Validation

Problems

Walked the redesigned flows with Clinical, CS, and Sales — the same groups whose questions surfaced the issue. Fitness pilot as a live check before medical.

Hazards

Already validated once on fitness. This project confirmed correct implementation on medical's higher-stakes Sleep/6MWT reports. A full summative HF study is the next step.

08Impact

Org and process: the six hazard controls are now a documented Figma library and Notion reference that design and engineering both pull from, beyond this project — built with real cross-functional buy-in from Head of Product, Clinical, Sales, CS, and Engineering on a surface where a misstep gets formally logged.

Product: launched on fitness first — user-reported issues dropped noticeably during that pilot, and Sales and CS reported fewer recurring questions about the flows that had prompted the work. That justified extending the model, plus the hazard controls for Sleep and 6MWT, to the medical dashboard.

Business: the medical dashboard, carrying these controls, is now piloting with prospective hospital clients — a new, higher-stakes client segment for that surface.

Still missing: hard usage metrics and a dedicated summative human-factors study for these Sleep and 6MWT reports specifically are still pending.

09Appendix: the risk framework

How OxiWear reasons about riskHow ISO 14971's risk chain and severity/probability scoring decide whether a hazard needs a control.

Following ISO 14971:2019, every issue traces through a chain. A use error — an action, or missed action, producing an unintended result — creates exposure to a hazard, a potential source of harm. That can become a hazardous situation: someone is actually exposed to it. If it plays out, that results in harm. A control is designed to break that chain — removing the use error, closing off the exposure, or stopping the hazardous situation from turning into harm.

Risk is severity combined with probability — and probability splits into how likely the hazardous situation is to occur, and how likely it is to then cause harm. Severity runs from negligible to catastrophic; a use error scoring at the top is a “critical task.” Whether a hazard needs a new control isn't severity alone — it's weighed against a threshold, but unevenly: high severity usually calls for a control even at low probability, since the consequence is too serious to leave to chance; low severity often doesn't, even at moderate probability, since the worst case is just an inconvenience.

A control isn't finished once it breaks the chain it was built for — it has to be run back through that same chain, checking whether the control itself introduces a new use error, a new hazard, or a new hazardous situation. The interruption-recovery control is a direct example: the original fix broke the chain it targeted (losing report progress on interruption), but retracing the fixed design through the chain again surfaced a new one — restoring straight into step two skipped the device-user confirmation, opening a new use-error path to configuring against the wrong patient. The final control closes that second chain too, always landing back on step one first before the specialist can proceed.