skills/healthcare-ui-design/references/design-tokens.md

18 KiB

Healthcare Design Tokens

Cross-platform token system for clinical-grade UI: Web (Bootstrap 5/Tabler + PHP) and Android (Jetpack Compose + Material 3).

Quick-Reference Token Table

Token Category Web (CSS Custom Property) Android (Compose) Value
Primary --clinical-primary ClinicalPrimary #2563EB
Secondary --clinical-secondary ClinicalSecondary #0F766E
Surface --clinical-surface ClinicalSurface #F8FAFC
Background --clinical-bg ClinicalBackground #F1F5F9
Critical --clinical-critical ClinicalCritical #DC2626
Warning --clinical-warning ClinicalWarning #D97706
Success --clinical-success ClinicalSuccess #059669
Info --clinical-info ClinicalInfo #0284C7
Text Primary --clinical-text ClinicalText #0F172A
Text Secondary --clinical-text-secondary ClinicalTextSecondary #334155
Text Muted --clinical-text-muted ClinicalTextMuted #64748B
Medication --clinical-medication ClinicalMedication #7C3AED
Spacing Base --space-base SpaceBase 4px / 4dp
Radius md --radius-md RadiusMd 8px / 8dp
Body Font Size --font-body bodyLarge 0.9375rem / 16sp

1. Color System

1.1 Core Semantic Palette

/* Web — add to your root stylesheet or Tabler custom override */
:root {
  /* Primary palette */
  --clinical-primary: #2563EB;       /* Trust blue — headers, primary buttons, nav */
  --clinical-primary-hover: #1D4ED8;
  --clinical-primary-light: #DBEAFE; /* Badges, light backgrounds */
  --clinical-secondary: #0F766E;     /* Clinical teal — secondary actions */
  --clinical-secondary-hover: #0D6963;
  --clinical-secondary-light: #CCFBF1;

  /* Surfaces */
  --clinical-surface: #F8FAFC;       /* Clean white — card backgrounds */
  --clinical-bg: #F1F5F9;            /* Soft gray — page background */
  --clinical-border: #E2E8F0;        /* Default borders */

  /* Feedback */
  --clinical-critical: #DC2626;      /* Danger, critical vitals, allergies */
  --clinical-critical-light: #FEF2F2;
  --clinical-warning: #D97706;       /* Approaching thresholds */
  --clinical-warning-light: #FFFBEB;
  --clinical-success: #059669;       /* Normal ranges, confirmations */
  --clinical-success-light: #ECFDF5;
  --clinical-info: #0284C7;          /* Informational, pending states */
  --clinical-info-light: #F0F9FF;

  /* Text hierarchy */
  --clinical-text: #0F172A;          /* Primary body text */
  --clinical-text-secondary: #334155;/* Secondary labels */
  --clinical-text-muted: #64748B;    /* Timestamps, metadata */
  --clinical-text-inverse: #FFFFFF;  /* Text on dark backgrounds */
}
// Android — HealthcareColors.kt
package com.app.ui.theme

import androidx.compose.ui.graphics.Color

object ClinicalColors {
    // Primary palette
    val Primary = Color(0xFF2563EB)
    val PrimaryHover = Color(0xFF1D4ED8)
    val PrimaryLight = Color(0xFFDBEAFE)
    val Secondary = Color(0xFF0F766E)
    val SecondaryHover = Color(0xFF0D6963)
    val SecondaryLight = Color(0xFFCCFBF1)

    // Surfaces
    val Surface = Color(0xFFF8FAFC)
    val Background = Color(0xFFF1F5F9)
    val Border = Color(0xFFE2E8F0)

    // Feedback
    val Critical = Color(0xFFDC2626)
    val CriticalLight = Color(0xFFFEF2F2)
    val Warning = Color(0xFFD97706)
    val WarningLight = Color(0xFFFFFBEB)
    val Success = Color(0xFF059669)
    val SuccessLight = Color(0xFFECFDF5)
    val Info = Color(0xFF0284C7)
    val InfoLight = Color(0xFFF0F9FF)

    // Text
    val Text = Color(0xFF0F172A)
    val TextSecondary = Color(0xFF334155)
    val TextMuted = Color(0xFF64748B)
    val TextInverse = Color(0xFFFFFFFF)
}

1.2 Clinical Status Colors

Status Foreground Background Usage
Vital Normal #059669 #ECFDF5 Within normal range
Vital Warning #D97706 #FFFBEB Approaching threshold
Vital Critical #DC2626 #FEF2F2 Outside safe range
Allergy Alert #DC2626 #FEF2F2 Allergy banners, badges
Medication #7C3AED #F5F3FF Pharma context, prescriptions
Lab Pending #0284C7 #F0F9FF Awaiting results
Lab Complete #059669 #ECFDF5 Results available
Discharged #64748B #F1F5F9 Inactive/historical records

1.3 Accessibility Compliance

All color pairs MUST meet WCAG 2.2 AA minimum contrast ratios:

  • Normal text (< 18pt): 4.5:1 contrast ratio minimum
  • Large text (>= 18pt or 14pt bold): 3:1 contrast ratio minimum
  • UI components / graphical objects: 3:1 against adjacent colors

Verified pairings:

Text Color Background Ratio Pass
#0F172A on #F8FAFC Surface 15.4:1 AA/AAA
#334155 on #F8FAFC Surface 8.5:1 AA/AAA
#FFFFFF on #2563EB Primary 4.6:1 AA
#FFFFFF on #DC2626 Critical 4.6:1 AA
#FFFFFF on #059669 Success 4.5:1 AA
#92400E on #FFFBEB Warning bg 7.1:1 AA/AAA

1.4 Color-Blind Safety

Never rely on red/green alone to distinguish clinical states. ALWAYS pair color with:

  • Text labels: "Normal", "Warning", "Critical" alongside color indicators
  • Icons: Check-circle, alert-triangle, x-circle
  • Patterns: Solid fill (critical), hatched (warning), outline (normal)
  • Shape: Circle (normal), triangle (warning), octagon (critical)

2. Typography Scale

2.1 Web (Bootstrap 5 / Tabler)

:root {
  --font-system: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --font-mono: "SF Mono", "Cascadia Code", "Fira Code", "Consolas", monospace;

  /* Page hierarchy */
  --font-h1: 1.75rem;     /* 28px — page titles */
  --font-h1-weight: 700;
  --font-h2: 1.25rem;     /* 20px — section headings */
  --font-h2-weight: 600;
  --font-h3: 1.1rem;      /* 17.6px — subsection headings */
  --font-h3-weight: 600;

  /* Body text */
  --font-body: 0.9375rem; /* 15px — primary body */
  --font-body-weight: 400;
  --font-small: 0.8125rem;/* 13px — secondary, captions */
  --font-small-weight: 400;
  --font-caption: 0.75rem;/* 12px — timestamps, metadata */
  --font-caption-weight: 400;

  /* Clinical specialties */
  --font-vital-value: 1.5rem;    /* 24px — vital sign readings */
  --font-vital-weight: 700;
  --font-patient-name: 1.125rem; /* 18px — patient display name */
  --font-patient-weight: 600;
  --font-mrn: 0.875rem;          /* 14px — MRN, IDs */
  --font-mrn-weight: 500;
  --font-mrn-spacing: 0.05em;    /* Wider tracking for readability */
}

/* Clinical value class */
.vital-value {
  font-family: var(--font-mono);
  font-size: var(--font-vital-value);
  font-weight: var(--font-vital-weight);
  line-height: 1.2;
}

.patient-name { font-size: var(--font-patient-name); font-weight: var(--font-patient-weight); }
.mrn-display { font-family: var(--font-mono); font-size: var(--font-mrn); font-weight: var(--font-mrn-weight); letter-spacing: var(--font-mrn-spacing); }

2.2 Android (Jetpack Compose — Material 3 Overrides)

// HealthcareTypography.kt
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

val HealthcareTypography = Typography(
    headlineLarge = TextStyle(fontSize = 28.sp, fontWeight = FontWeight.W400, lineHeight = 36.sp),
    headlineMedium = TextStyle(fontSize = 24.sp, fontWeight = FontWeight.W400, lineHeight = 32.sp),
    titleLarge = TextStyle(fontSize = 22.sp, fontWeight = FontWeight.W400, lineHeight = 28.sp),
    titleMedium = TextStyle(fontSize = 16.sp, fontWeight = FontWeight.W500, lineHeight = 24.sp),
    bodyLarge = TextStyle(fontSize = 16.sp, fontWeight = FontWeight.W400, lineHeight = 24.sp),
    bodyMedium = TextStyle(fontSize = 14.sp, fontWeight = FontWeight.W400, lineHeight = 20.sp),
    labelLarge = TextStyle(fontSize = 14.sp, fontWeight = FontWeight.W500, lineHeight = 20.sp),
    labelSmall = TextStyle(fontSize = 11.sp, fontWeight = FontWeight.W500, lineHeight = 16.sp),
)

// Clinical-specific text styles (extend beyond M3 type scale)
object ClinicalTextStyles {
    val VitalValue = TextStyle(
        fontFamily = FontFamily.Monospace,
        fontSize = 24.sp,
        fontWeight = FontWeight.W700,
        lineHeight = 28.sp
    )
    val PatientName = TextStyle(
        fontSize = 18.sp,
        fontWeight = FontWeight.W600,
        lineHeight = 24.sp
    )
    val MrnDisplay = TextStyle(
        fontFamily = FontFamily.Monospace,
        fontSize = 14.sp,
        fontWeight = FontWeight.W500,
        letterSpacing = 0.8.sp
    )
}

3. Spacing System

Base unit: 4px (web) / 4dp (Android). All spacing values are multiples of 4.

Token Value CSS Variable Compose Use Case
xs 4 --space-xs: 4px 4.dp Icon-to-text gap, tight inline spacing
sm 8 --space-sm: 8px 8.dp Related element gap, badge padding
md 12 --space-md: 12px 12.dp List item vertical padding, form field gap
lg 16 --space-lg: 16px 16.dp Card internal padding, section padding
xl 24 --space-xl: 24px 24.dp Between card groups, column gutters
2xl 32 --space-2xl: 32px 32.dp Major section separation
3xl 48 --space-3xl: 48px 48.dp Page-level vertical margins

Component-Specific Spacing

Component Padding Gap
Card (web/Android) 16px / 16dp all sides 24px / 24dp between cards
List item 12px vertical, 16px horizontal 1px divider or 8px gap
Form fields 0 internal (handled by component) 12px between fields
Section groups 0 (cards handle own padding) 32px between sections
Button group 8px between buttons N/A
Alert banners 12px vertical, 16px horizontal 8px icon-to-text

Touch Target Minimums

Platform Minimum Preferred (clinical) Note
Web 44 x 44px 48 x 48px WCAG 2.2 Level AA target size
Android 48 x 48dp 56 x 56dp Preferred for bedside/clinical use

Clinical environments use gloved hands and stressed motor control — always prefer the larger target size.


4. Elevation and Shadows

4.1 Web (CSS Shadows)

:root {
  --shadow-0: none;                                         /* Level 0: inline content */
  --shadow-1: 0 1px 2px 0 rgba(0, 0, 0, 0.05);           /* Level 1: cards, tiles */
  --shadow-2: 0 4px 6px -1px rgba(0, 0, 0, 0.07),
              0 2px 4px -2px rgba(0, 0, 0, 0.05);         /* Level 2: dropdowns, popovers */
  --shadow-3: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
              0 4px 6px -4px rgba(0, 0, 0, 0.05);         /* Level 3: modals, dialogs */
}
Level Shadow Token Use Case
0 --shadow-0 Table rows, inline content, list items
1 --shadow-1 Cards, panels, stat tiles
2 --shadow-2 Dropdown menus, popovers, tooltips
3 --shadow-3 Modals, confirmation dialogs, medication checks

4.2 Android (Material 3 Elevation)

Level Elevation Compose Use Case
0 0dp elevation = 0.dp Flat surfaces, list items
1 1dp elevation = 1.dp Cards, ElevatedCard
2 3dp elevation = 3.dp Navigation bars, bottom sheets
3 6dp elevation = 6.dp FAB, extended FAB
4 8dp elevation = 8.dp Dialogs, modal bottom sheets

5. Border Radius

Token Web Android Use Case
sm 4px 4.dp Input fields, text areas, inline badges
md 8px 8.dp Cards, buttons, dropdown menus
lg 12px 12.dp Large cards, image containers
xl 16px 16.dp Modals, bottom sheets
pill 9999px CircleShape Status badges, vital indicators, tags
:root {
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-pill: 9999px;
}
object ClinicalShapes {
    val Small = RoundedCornerShape(4.dp)
    val Medium = RoundedCornerShape(8.dp)
    val Large = RoundedCornerShape(12.dp)
    val ExtraLarge = RoundedCornerShape(16.dp)
    val Pill = CircleShape
}

Mapping: Cards use md. Buttons use md. Input fields use sm. Clinical status badges use pill. Modals use xl.


6. Iconography

Healthcare-Specific Icons

Category Icons Web Source Android Source
Clinical Stethoscope, heartbeat, pill, syringe, thermometer, lungs, brain, bone, eye, tooth Tabler Icons / Bootstrap Icons Material Symbols (Outlined)
Status Check-circle, x-circle, alert-triangle, info, clock, shield Tabler Icons Material Symbols
Actions Plus, edit, trash, search, filter, sort, download, print, share Tabler Icons Material Symbols

Sizing

Context Web Android When
Inline/compact 16px 20dp Inside buttons, badges, table cells
Default 20px 24dp Navigation, list leading icons
Emphasis 24px 28dp Card headers, empty states
Hero 48px 48dp Dashboard KPI icons, onboarding

Rules: Icons must have aria-hidden="true" when decorative, or a descriptive aria-label when functional. On Android, use contentDescription for accessibility. Never use icons alone for critical clinical actions — always pair with a text label.


7. Animation and Motion

7.1 Web Transitions

:root {
  --duration-micro: 150ms;   /* Button hover, focus rings, toggles */
  --duration-standard: 300ms; /* Panel open/close, state changes */
  --duration-page: 400ms;     /* Page transitions, route changes */
  --easing-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --easing-decelerate: cubic-bezier(0, 0, 0.2, 1);   /* Elements entering */
  --easing-accelerate: cubic-bezier(0.4, 0, 1, 1);    /* Elements exiting */
}

/* Usage */
.card { transition: box-shadow var(--duration-micro) var(--easing-standard); }
.panel-expand { transition: max-height var(--duration-standard) var(--easing-decelerate); }

7.2 Android Motion (Material 3 Spring-Based)

object ClinicalMotion {
    // Enters — elements appearing on screen
    val EmphasizedDecelerate = tween<Float>(
        durationMillis = 400,
        easing = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1.0f)
    )
    // Exits — elements leaving the screen
    val EmphasizedAccelerate = tween<Float>(
        durationMillis = 200,
        easing = CubicBezierEasing(0.3f, 0.0f, 0.8f, 0.15f)
    )
    // State changes — color, size, elevation shifts
    val Standard = tween<Float>(
        durationMillis = 300,
        easing = CubicBezierEasing(0.2f, 0.0f, 0.0f, 1.0f)
    )
}

7.3 Clinical Motion Rules

  • Critical alerts: NO animation delay. Appear instantly (duration: 0ms). Patient safety cannot wait for a fade-in.
  • Loading/skeleton states: Use a pulse animation (opacity oscillation 0.4 to 1.0, 1.5s loop).
  • Vital sign updates: Subtle color transition (300ms) when value changes category (normal to warning).
  • Never animate medication confirmation dialogs — they must be immediately readable.
  • Reduce motion: Respect prefers-reduced-motion (web) and accessibility motion settings (Android). Replace animations with instant state changes.
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

8. Breakpoints

8.1 Web (Bootstrap 5 Standard)

Breakpoint Min Width Grid Columns Clinical Layout
xs 0 1 Stacked cards, single-column patient list
sm 576px 1-2 Patient list with minimal detail
md 768px 2 Sidebar nav + content, tablet portrait
lg 992px 2-3 Full clinical detail views, list + detail
xl 1200px 3 Three-panel: nav + list + detail
xxl 1400px 3-4 Dashboard with multi-widget grid

Optimization targets:

  • Patient lists usable at ALL breakpoints (xs through xxl)
  • Clinical detail views (vitals, medication charts) optimized for >= 992px (tablet landscape and above)
  • Admin dashboards designed for >= 1200px, gracefully collapse below

8.2 Android (WindowSizeClass)

Class Width Columns Layout Strategy
Compact < 600dp 1 Single-column patient view, bottom nav, full-screen forms
Medium 600-840dp 2 List-detail split (40/60), rail nav, side-by-side vitals
Expanded > 840dp 3 Three-panel: nav rail + patient list + detail pane
// Usage in Compose
@Composable
fun PatientScreen(windowSizeClass: WindowSizeClass) {
    when (windowSizeClass.widthSizeClass) {
        WindowWidthSizeClass.Compact -> PatientSingleColumn()
        WindowWidthSizeClass.Medium -> PatientListDetail()
        WindowWidthSizeClass.Expanded -> PatientThreePanel()
    }
}

Token Application Checklist

When building a new healthcare screen, verify:

  1. All colors reference token variables (no hardcoded hex in components)
  2. Typography uses defined scale only (no arbitrary font sizes)
  3. Spacing uses 4px/4dp multiples exclusively
  4. Touch targets meet 44px/48dp minimums
  5. Color pairs pass WCAG 2.2 AA contrast check
  6. Color-blind safe: every color meaning has a text/icon backup
  7. Critical alerts have zero animation delay
  8. prefers-reduced-motion / Android accessibility settings are respected
  9. Breakpoints handle all clinical layouts from mobile to desktop
  10. Clinical values (vitals, MRN) use monospace font tokens