Skip to main content

08 — Frontend Design Guidelines

Status: populated Last updated: 2026-04-18 Companion: 09 frontend-workflows · 01 enterprise-architecture · 17 technology-stack · 13 security-compliance-tenancy · 16 offline-first

1. Surfaces and app shells

Ghasi-eHealth ships multiple coordinated UI surfaces — each inherits one design system, tokens, and accessibility baseline, but presents density and navigation tuned to the persona.

SurfaceTarget personaTechDensityOffline capability
Clinician Web (EHR)Doctor, nurse, charge nurse, residentNext.js 16 (App Router), MUI v6, TanStack Query, ZustandComfortable / Compact togglePartial (worklists + chart read)
Registration Station (Desktop)Reg. clerk, cashier, triage nurseElectron + Vite, React Router, better-sqlite3, Windows/macOS/LinuxCompact (dense forms)Full offline (SQLite outbox)
Patient Portal (Web)Patient, caregiver, guardianNext.js 16, MUI v6, next-authComfortable, generous spacingRead-only cache of last-seen
Patient Mobile (iOS/Android)PatientReact Native / Expo (Expo Router), Keycloak OIDCThumb-first, large hit targetsFull offline read, queued writes
Provider Mobile (iOS/Android)Field nurse, CHW, outreachReact Native / Expo, Realm local DB, MAC-signed syncDense (field-ops)Full offline clinic
Lab / Pharmacy PortalsLab tech, pharmacistNext.js 16CompactPartial (queue replay)
Virtual Care RoomClinician + patientNext.js + Jitsi SDK (WebRTC)Distraction-minimisedOnline-only
Platform Admin ConsoleTenant admin, super-adminNext.js 16Compact tables, audit-log heavyOnline-only

All surfaces share the same design tokens, component primitives, and i18n bundle. Divergence is allowed only for density mode, navigation pattern, and offline affordances.

2. Frontend clean architecture (shared layout)

/app # Next.js route segments (App Router)
/components # Presentational components
/components/server # Server components (RSC)
/hooks # Orchestration (TanStack Query wrappers)
/services # API ports (typed clients per service)
/state # Zustand stores per domain
/lib/domain # Pure TS primitives — Money, FHIR refs, ABAC eval, terminology codes, RRULE
/lib/adapters # IndexedDB (Dexie), Service Worker, offline outbox, Realm (mobile)
/fhir # FHIR resource helpers, builders, validators
/i18n # ICU messages per locale
/styles # Tokens, tailwind preset, global base
/types # Shared TS types mirroring service DTOs

Rules:

  • lib/domain has no React, Next.js, or fetch. Pure TypeScript.
  • services/ defines ports; lib/adapters/ supplies concrete implementations bound at bootstrap.
  • Components depend on hooks/ + services/ only. Never direct fetch in a component.
  • FHIR resource shapes are imported from @ghasi/shared-types — no ad-hoc types.

3. Design tokens

Tokens live in CSS custom properties and are exported as a Tailwind preset and MUI theme builder. All component styling must consume tokens — no raw hex, no magic spacing.

3.1 Color — clinical semantic palette

:root {
/* Surfaces */
--color-bg: oklch(98% 0.005 260);
--color-surface: oklch(99% 0.003 260);
--color-surface-alt: oklch(95% 0.01 260);

/* Text */
--color-text: oklch(20% 0.01 260);
--color-text-muted: oklch(42% 0.02 260);
--color-text-inverse: oklch(98% 0 0);

/* Brand (platform identity) */
--color-brand: oklch(52% 0.14 235);
--color-brand-fg: oklch(99% 0 0);

/* Clinical semantic — red / amber / green traffic */
--color-critical: oklch(52% 0.22 25); /* stat orders, alerts, allergies */
--color-warning: oklch(72% 0.18 80); /* due soon, caution */
--color-success: oklch(55% 0.16 145); /* normal, verified, in-range */
--color-info: oklch(58% 0.13 235); /* informational */

/* Clinical status-specific (safety-critical, never dual-meaning) */
--color-stat: var(--color-critical); /* STAT order label */
--color-abnormal-h: oklch(48% 0.20 25); /* High flag on result */
--color-abnormal-l: oklch(48% 0.20 240); /* Low flag on result */
--color-allergy: oklch(48% 0.22 15); /* Allergy banner */
--color-in-range: var(--color-success);

/* Charting */
--color-chart-1: oklch(60% 0.14 235);
--color-chart-2: oklch(60% 0.14 155);
--color-chart-3: oklch(60% 0.14 55);
--color-chart-4: oklch(60% 0.14 320);
}

.theme-dark {
--color-bg: oklch(14% 0.01 260);
--color-surface: oklch(18% 0.01 260);
--color-text: oklch(95% 0.005 260);
/* ... semantic tokens re-tuned for ≥ 4.5:1 contrast in dark */
}

.theme-high-contrast {
/* WCAG 2.2 AAA — used by accessibility toggle */
--color-text: oklch(5% 0 0);
--color-bg: oklch(100% 0 0);
--color-brand: oklch(30% 0.2 235);
}

Rules:

  • Semantic tokens are one-directional--color-critical is used only for critical. Never to stylize branding.
  • Red/green alone is not the carrier of meaning — every status badge pairs color with icon and text label (colorblind policy).
  • Dark mode is opt-in; daytime clinical workflows default light. Never auto-switch based on OS — ward lighting and patient-in-bed contexts differ.

3.2 Typography — bi-directional

:root {
--font-sans-ltr: 'InterVariable', system-ui, sans-serif;
--font-sans-rtl: 'Vazirmatn', 'Noto Naskh Arabic', 'InterVariable', sans-serif;
--font-mono: 'JetBrainsMono', 'Courier New', monospace;
--font-numeric: 'InterVariable'; /* tabular-nums for vitals and dose */

--text-xs: clamp(0.72rem, 0.7rem + 0.1vw, 0.78rem);
--text-sm: clamp(0.82rem, 0.8rem + 0.15vw, 0.9rem);
--text-base: clamp(0.94rem, 0.9rem + 0.25vw, 1.05rem);
--text-lg: clamp(1.08rem, 1rem + 0.4vw, 1.25rem);
--text-xl: clamp(1.25rem, 1.15rem + 0.5vw, 1.5rem);
--text-h2: clamp(1.5rem, 1.3rem + 1vw, 2rem);
--text-h1: clamp(2rem, 1.7rem + 1.5vw, 3rem);

--line-height-tight: 1.18;
--line-height-base: 1.55;
--line-height-loose: 1.75; /* for Dari/Pashto paragraph blocks */
}

html[dir='rtl'] { font-family: var(--font-sans-rtl); }
html[dir='ltr'] { font-family: var(--font-sans-ltr); }

Rules:

  • html[dir] is set by the server per locale.
  • Every layout uses logical properties: padding-inline, margin-inline-start, inset-inline-end, text-align: start | end. No left/right.
  • Direction-implying icons (chevrons, undo/redo) flipped via [dir='rtl'] .flip-on-rtl { transform: scaleX(-1); }.
  • Mixed-direction blocks (English drug name inside Dari sentence) use Unicode bidi isolates (<bdi>, isolate-override). Tested in Storybook bidi stories.
  • Clinical numeric readouts (vitals, dose) always use font-variant-numeric: tabular-nums to keep table columns aligned.

3.3 Space, radius, shadow, motion

:root {
--space-0: 0; --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem;
--space-4: 1rem; --space-5: 1.25rem; --space-6: 1.5rem; --space-8: 2rem;
--space-10: 2.5rem; --space-12: 3rem; --space-16: 4rem;

--radius-xs: 2px; --radius-sm: 4px; --radius-md: 8px; --radius-lg: 12px; --radius-pill: 999px;

--shadow-1: 0 1px 2px oklch(0% 0 0 / 0.06), 0 1px 4px oklch(0% 0 0 / 0.04);
--shadow-2: 0 4px 16px oklch(0% 0 0 / 0.08);
--shadow-focus: 0 0 0 3px oklch(52% 0.14 235 / 0.35);

--duration-instant: 80ms;
--duration-fast: 150ms;
--duration-base: 220ms;
--duration-slow: 360ms;
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
--ease-entrance: cubic-bezier(0, 0, 0, 1);
--ease-emphasized: cubic-bezier(0.2, 0, 0, 1.2);
}

3.4 Density modes

Clinician workflows demand high information density, but patient-facing surfaces must breathe. A single data-density="compact|comfortable" attribute on the root switches token multipliers.

ModeDefault forRow heightFont baseButton padding-inline
comfortablePatient portal, patient mobile, onboarding48 px--text-base--space-5
compactClinician web, registration station, admin console32 px--text-sm--space-3

Clinicians can toggle modes per device via user preference; preference is synced in the user profile (not per-session).

4. Iconography

  • Primary set: FontAwesome Pro Sharp (licensed) — consistent stroke weight across the platform.
  • Clinical custom set: bespoke SVG icons under @ghasi/icons-clinical for clinical primitives FontAwesome lacks (syringe types, specimen tubes, dose calculator, stethoscope/pulse-ox). 24 × 24 grid, 1.5 px stroke, variable stroke weight on hover.
  • Never raster — SVG only, currentColor-driven.
  • Directional semanticsfa-arrow-right tagged .flip-on-rtl. Non-directional icons (heart, check, warning) never flipped.
  • Pairing rule — every status pairs icon + color + short text label. Icons alone are not the carrier of meaning (colorblind / low-contrast).

5. Layout grid

  • Desktop clinician (≥ 1280 px): 12-column grid, 24 px gutter, 80 px side rails. Chart pages use a 3-region layout: 280 px left rail (problem/allergy/med summary), fluid center (active content), 320 px right rail (orders/results pane).
  • Laptop (1024 – 1279 px): 12-column, 16 px gutter, right rail collapses to a tab strip.
  • Tablet (768 – 1023 px): 8-column, single primary + drawer left rail.
  • Phone (< 768 px): 4-column, bottom tab nav, drawers for secondary.
  • Print (A4 / Letter): stripped theme, tokens downshift (serif typography, 11 pt base, black only unless semantic color carries meaning — e.g., allergy banner).

6. Accessibility baseline

Target WCAG 2.2 Level AA platform-wide; aspire AAA on critical read/print paths (allergy banner, medication list, allergy printout).

ConcernRule
Contrast≥ 4.5:1 body, ≥ 3:1 large text and UI components
Keyboard100% of flows reachable via keyboard; visible focus ring; Esc closes any dialog
Screen readerSemantic HTML first; ARIA only where semantics are not natively expressible; live regions for result arrival and order confirmation
Reduced motionAll motion gated on @media (prefers-reduced-motion)
Target sizeMinimum 24 × 24 CSS px (WCAG 2.5.8); clinical primary buttons 44 × 44
Languagelang attr per passage; bilingual strings in forms tagged with <bdi>
Form errorsaria-describedby to message; programmatic label on every input
Live regionsVital-sign alerts and critical-result arrival announced via role="alert"
Zoom200% zoom + 320 px reflow without loss of function
MediaCaptions on all educational video; transcripts downloadable
Allergy bannerAlways at top of chart, high-contrast, present on print; announced first by screen readers

Automated gate: axe-playwright on every PR. Violations at serious or critical block merge. Manual NVDA + VoiceOver + TalkBack review per release.

7. Internationalization

  • Locales: en, ps-AF (Pashto, RTL), fa-AF (Dari, RTL), ar-AE (Arabic, RTL), plus future uz, tk, ur.
  • Format: ICU MessageFormat; plural + gender rules supported.
  • Calendars: Gregorian (storage + display default), Solar Hijri (jalali, Afghanistan civil calendar), Hijri lunar (religious). User preference selects display calendar; storage is ISO-8601 UTC always.
  • Numbers and dates: Intl.NumberFormat / Intl.DateTimeFormat; Arabic-Indic digits honored by locale. Clinical numeric fields always in Latin digits with lang="en" override to prevent miscommunication.
  • Fallback chain: User pref → facility default → tenant default → en.

8. Component system

8.1 Primitives (shared across all surfaces)

Button, IconButton, Link, Input, NumericInput, Textarea, Select, Combobox, Autocomplete, Checkbox, Radio, Switch, Slider, DatePicker (Gregorian + Jalali + Hijri), TimePicker, DurationPicker, Dialog, Drawer, Tooltip, Popover, Toast, Banner, Skeleton, Avatar, Badge, Tag, Chip, Card, Surface, Divider, Stack, Cluster, Grid, Tabs, Accordion, Tree.

8.2 Clinical composites

  • AllergyBanner — mandatory top-of-chart banner; renders even on print; high-contrast; announces allergen list.
  • VitalsPanel — tabular vitals with in-range / out-of-range visual encoding (icon + color + label).
  • OrderEntryCard — unified order entry for lab / medication / radiology / referral with STAT emphasis and dose-safety warnings.
  • ResultsTable — FHIR Observation-driven; H/L/CRIT flags; reference-range sparkline.
  • MedicationList — active meds with reconciliation state, therapeutic class grouping.
  • ChartTimeline — longitudinal chart navigator; encounter selector; problem / med / visit pin.
  • EncounterHeader — patient demographics condensed; provider identity; location; encounter type.
  • ConsentPrompt — structured consent with versioned policy text.
  • SpecimenLabel — print-ready label component; QR + barcode; tolerant to RTL facility names.

8.3 Patterns

  • Compound componentsTabs.Root / Tabs.List / Tabs.Trigger / Tabs.Content pattern for shared state.
  • Headless first — primitives wrap Radix UI for accessibility; MUI v6 provides layout/shell surface.
  • Every form field supports optional lang override and dir override for clinically significant Latin content inside RTL forms.

9. Offline UI patterns

  • Sync Status Pill in app shell — states: online, syncing, offline, paused, conflicts. Click opens Sync Center.
  • Offline Banner (non-blocking) on partial-offline surfaces — "Writing locally. Will sync when connected."
  • Outbox Drawer — pending mutations list with per-aggregate conflict resolution UI.
  • Conflict Resolver — side-by-side diff with Keep Mine / Keep Server / Merge per per-aggregate policy (see 16 offline-first §5).
  • Bundle / Cache Manager — storage usage, pinned patients, eviction policy, emergency purge.
  • Tamper Warning — modal-blocking when signed sync envelope fails verification; mandatory re-enrol.
  • Optimistic UI — mutations apply locally and mark with a pending-dot until reconciled.

10. AI affordances (ai-gateway-service)

Ghasi-eHealth positions AI as explicit, auditable assistance — not autonomous action.

RuleRationale
Explicit triggerNo AI action without an explicit user click. Never auto-populate clinical fields.
Provenance badgeEvery AI-produced artifact shows model, prompt template version, time, and reviewer (if signed).
HITL signatureClinician must accept or reject. Accept = counter-signature. Rejection is logged.
Streaming UXToken-by-token with a visible Cancel button.
Refusal UXNeutral, non-judgmental message; user can rephrase.
Local-vs-cloud indicatorWhen offline AI is used (reduced-capability local model), badge says "Local model — limited accuracy".
Cost visibilityAdmin view shows per-tenant AI usage and budget.
Acceptance patternAI suggestions rendered with subtle dashed border until accepted.

11. Page architecture

  • Layouts — App shell (sidebar nav + top bar + sync pill); minimal shell (auth, consent intake); immersive shell (virtual visit room); print shell (clinical document).
  • Loading — route-level Suspense + skeletons; never spinners over already-rendered content.
  • Empty states — every list has an opinionated empty state with one clear next action, localized to the tenant's likely context (e.g., "No patients waiting. Register new patient").
  • Error states — problem+json codes mapped to user-friendly messages with recovery affordance; never raw stack. Never leak PHI into error copy.
  • Notifications — toast for ephemeral; in-app inbox for persistent; communication-service handles outbound.

12. Performance budgets

PageLCP (p75)INPCLSJS (gzip)
Patient portal home< 1.8 s< 200 ms< 0.05< 160 kb
Clinician chart open< 2.5 s (PHI-heavy)< 200 ms< 0.1< 320 kb
Registration form< 1.5 s< 150 ms< 0.05< 200 kb
Virtual care room< 3 s (WebRTC negotiation in-flight)< 450 kb
Admin console< 2.5 s< 200 ms< 0.1< 380 kb

Techniques: route-level code-split, dynamic import of heavy editors (rich-text notes, imaging viewer), explicit width/height on hero media, preload only critical font weight, no third-party scripts in clinical shells.

13. Testing

  • Component tests: Vitest + Testing Library; behavior-based.
  • Visual regression: Playwright screenshots in LTR + RTL + dark + high-contrast across 320, 768, 1024, 1440, 1920.
  • E2E: Playwright + Detox (mobile) per journey catalogue in 07 epics §4.
  • Accessibility: axe-playwright gate; serious/critical violations block merge.
  • Offline: context.setOffline(true) + IndexedDB/Realm fixtures + service-worker tests.
  • Bidi: Every component has an RTL story in Storybook; visual regression in both directions.

14. State management conventions

  • Server data: TanStack Query — typed query keys per service; mutations feed the offline outbox adapter on offline-capable surfaces.
  • Client UI state: Zustand stores per surface (chart store, order entry store, sync store, virtual-care store).
  • URL state: filters, sort, pagination, active tab, patient context live in URL search params.
  • Form state: React Hook Form + Zod schemas mirroring backend DTOs.
  • No global Redux store.

15. Rationale

A single token set + component system lets 27 services converge on one clinical UX with predictable behaviour in Pashto, Dari, Arabic, and English. Direction-agnostic CSS makes RTL a layout attribute, not a branch. Density modes let the same React tree serve both a patient on a phone and a busy triage nurse at a workstation. Offline, AI, and accessibility are treated as first-class design concerns because in the Afghanistan reference context they are the difference between a platform that works in a district hospital and one that does not.

16. Open questions

  • Decide whether the desktop registration station Electron app should host the same Next.js bundle (webview) or ship a tailored React+Vite bundle for startup speed.
  • Final form-factor for provider mobile on low-end Android (< 3 GB RAM) in outreach contexts.