WCAG Guidelines
CPR should target WCAG 2.1 Level AA compliance. This document covers the most relevant guidelines for the application, with specific attention to CPR's design system (primary color #117ea7, Tailwind CSS utility classes, and clinical form patterns).
Color Contrast
CPR Primary Color: #117ea7
The primary brand color #117ea7 (a teal/blue) is used throughout the application for buttons, modal headers, and focus rings.
Contrast ratios with common backgrounds:
| Foreground | Background | Ratio | WCAG AA (Normal Text) | WCAG AA (Large Text) |
|---|---|---|---|---|
#117ea7 | White #ffffff | ~4.0:1 | Fails (needs 4.5:1) | Passes (needs 3:1) |
White #ffffff | #117ea7 | ~4.0:1 | Fails | Passes |
#117ea7 | #f9fafb (gray-50) | ~3.9:1 | Fails | Passes |
Recommendations:
- Use
#117ea7on white only for large text (18px+ or 14px+ bold), which is how CPR uses it for button text and modal headers withfont-semiboldorfont-bold. - For small body text, consider a darker variant (e.g.,
#0d5f80) that achieves 7:1+ contrast. - The focus ring
focus:ring-[#117ea7]/10is purely decorative (reinforced by the border change) and does not carry meaning alone, so its lower opacity is acceptable.
Other Color Considerations
- Error red (
bg-red-600,text-red-500) -- Tailwind'sred-600on white has a ratio of ~4.6:1 (passes AA).red-500is ~4.0:1 (borderline -- use for large text or alongside icons). - Success green (
bg-green-600) -- Tailwind'sgreen-600on white has ~4.5:1 ratio (passes AA). - Gray text (
text-gray-600) -- Tailwind'sgray-600on white has ~5.7:1 ratio (passes AA). - Disabled state (
disabled:opacity-50) -- Disabled elements are exempt from contrast requirements per WCAG, but should still be visually distinguishable.
Never Rely on Color Alone
The UiSnackBar uses color to distinguish message types, but also includes text content. Ensure all status indicators include a text or icon supplement:
<!-- Good -- color + text -->
<UiBadge variant="success">ACTIVE</UiBadge>
<UiBadge variant="warning">PENDING</UiBadge>
<!-- Avoid -- color only -->
<div :class="{ 'bg-green-500': active, 'bg-red-500': !active }" />Form Field Requirements
Labels
Every form input must have a visible label. The UiInput component handles this automatically:
<!-- UiInput generates a proper label-input association -->
<UiInput
v-model="patient.last_name"
label="Last Name"
required
/>This renders:
<label for="input-abc1234" class="text-xs font-bold text-gray-700 ...">
Last Name
<span class="text-red-500">*</span>
</label>
<input id="input-abc1234" type="text" ... />Required Field Indicators
CPR marks required fields with a red asterisk (*). This is a common convention, but screen reader users need additional context. Enhance with aria-required:
<!-- Recommended enhancement to UiInput -->
<input
:id="id"
:aria-required="required"
:aria-invalid="!!error"
:aria-describedby="error ? `${id}-error` : undefined"
...
/>
<div v-if="error" :id="`${id}-error`" class="mt-1 text-xs text-red-500">
{{ error }}
</div>Error Messaging
Validation errors should be:
- Visible -- Displayed below the relevant field (CPR does this).
- Descriptive -- Include the field name and what is wrong: "Patient Name is required" not just "Required".
- Programmatically associated -- Connected to the input via
aria-describedby. - Announced -- Use
aria-live="polite"on the error container oraria-invalidon the input.
CPR's validation rules already produce descriptive messages:
// "Patient Name is required" -- clear and descriptive
required('Patient Name');
// "Amount must be at least 1" -- includes the constraint
minValue('Amount', 1);Keyboard Navigation
Focus Order
The tab order should follow the visual layout of the page. CPR uses standard HTML flow, which naturally creates a logical tab order. Avoid using tabindex values greater than 0, which override the natural order.
Interactive Elements
All interactive elements must be keyboard-accessible:
| Element | Expected Keyboard Behavior |
|---|---|
UiButton | Enter or Space activates |
UiInput | Tab focuses, typing enters text |
UiModal close button | Escape closes the modal |
UiTable clickable rows | Should support Enter to activate |
UiDropdown | Arrow keys to navigate, Enter to select |
UiSearchSelect | Type to filter, arrow keys to navigate |
UiPagination | Tab between page buttons, Enter to navigate |
Focus Visibility
CPR's UiInput has clear focus styles:
focus:bg-white focus:outline-none focus:border-[#117ea7] focus:ring-4 focus:ring-[#117ea7]/10Ensure all interactive elements have visible focus indicators. Never use outline: none without providing an alternative focus style.
Screen Reader Considerations
Headings
Use a logical heading hierarchy on each page:
<template>
<h1>Patient Records</h1> <!-- Page title -->
<h2>Search and Filter</h2> <!-- Section -->
<h2>Patient List</h2> <!-- Section -->
<h3>Juan Dela Cruz</h3> <!-- Patient name in detail view -->
</template>Tables
The UiTable component uses proper <table>, <thead>, <th>, <tbody>, and <td> elements. Enhance with scope attributes:
<th scope="col">Patient Name</th>
<th scope="col">Status</th>Landmarks
Use ARIA landmarks or semantic elements to structure the page:
<template>
<nav aria-label="Main navigation">...</nav>
<main>
<h1>Queue Management</h1>
<!-- page content -->
</main>
</template>Testing for WCAG Compliance
Automated Tools
- axe DevTools -- Browser extension that scans for WCAG violations.
- Lighthouse -- Built into Chrome DevTools, includes accessibility audit.
- vue-axe -- Vue plugin that reports accessibility issues in the browser console during development.
Manual Testing
- Navigate the entire application using only the keyboard (Tab, Enter, Escape, Arrow keys).
- Use a screen reader (VoiceOver on macOS, NVDA on Windows) to verify that content is announced correctly.
- Test with browser zoom at 200% to ensure layout does not break.
- Verify that all form fields are operable without a mouse.
