work / fhir-form-engine
FHIR Form Engine
Renders FHIR R4 Questionnaires as accessible, scored forms.
- date
- status
- live
- tech
-
- React
- TypeScript
- React Aria
- FHIRPath
- Zustand
- Vite
- Playwright
A client-side, schema-driven renderer for FHIR R4 Questionnaire resources. Point it at
a Questionnaire and it produces an accessible, validated form, computes scores live, and
emits a spec-valid QuestionnaireResponse. Round-trip fidelity means a previously captured
response can be re-imported and edited.
Not for clinical use. This is a demonstration and portfolio project. Nothing it renders, scores, or exports is suitable for clinical, diagnostic, or treatment purposes.
The problem
Healthcare systems exchange forms as a standardized data format called a FHIR
Questionnaire. The format is well defined, but turning one into a form a person can actually
use is where it gets hard: the fields are described abstractly, questions appear and disappear
based on earlier answers, responses have to be scored, and the whole thing has to be
accessible enough for clinical settings. General-purpose form libraries do not read FHIR,
and most FHIR tooling is server-side. This project covers the rendering layer in between.
Normalize first
A FHIR Questionnaire is deliberately flexible, which also makes it messy: the same concept
can be expressed several ways, scoring weights live in one of two different extensions
depending on the version, and conditional-display rules use a small expression language of
their own. Reading raw FHIR directly inside the rendering code produces a tangle that is
hard to test.
The main design decision was to normalize first. A single parsing layer resolves all of that variability into one strongly-typed internal model, and nothing downstream ever touches raw FHIR again. Rendering, validation, and scoring all work against the normalized model. That one boundary keeps the rest of the system simple.
Accessibility
Forms like these get used with screen readers and keyboards, so the components are built on React Aria. The harder parts were beyond the widgets: an error-summary pattern that moves focus on a failed submit, and a live region that announces when conditional questions appear or disappear, so a screen-reader user does not silently gain or lose fields. An axe audit runs in CI against every bundled form in its initial, expanded, and error states.
Architecture
Four layers with one-way dependencies: parse, logic, state, render. FHIR’s variability is isolated in one exhaustively tested layer, all form logic stays pure and unit-testable, and the component layer focuses on accessibility. Scoring evaluates the questionnaire’s own weights via FHIRPath, so adding an instrument does not require new scoring code.
Scope and next steps
This implements a subset of the specification and is labeled as a demonstration, not a clinical product. Next: repeating question groups, terminology-server expansion for answer sets, server-side validation through a proxy, and multi-language rendering.