Design Principles for Accessibility
Accessible design is not a checklist of restrictions — it is a set of principles that produce better, more usable interfaces for everyone. The principles below address the most common visual and interaction design decisions that affect accessibility: colour and contrast, text sizing, focus states, responsive layout, and motion.
Colour contrast
DesignerColour contrast is the ratio of the relative luminance of the lighter colour to the darker colour. It ranges from 1:1 (no contrast — identical colours) to 21:1 (maximum contrast — black on white).
Contrast requirements
| Target | Text size | Minimum ratio |
|---|---|---|
| WCAG AA — normal text | Under 18pt (24px) regular, or 14pt (18.67px) bold | 4.5:1 |
| WCAG AA — large text | 18pt+ (24px+) regular, or 14pt+ (18.67px+) bold | 3:1 |
| WCAG AA — UI components | Borders of inputs, icons, focus rings | 3:1 |
| WCAG AAA — normal text | Under 18pt regular, or 14pt bold | 7:1 |
| WCAG AAA — large text | 18pt+ regular, or 14pt+ bold | 4.5:1 |
The site you are reading uses AAA contrast targets throughout.
Contrast examples
These swatches show the site’s colour tokens at their design-target contrast ratios:
Primary text on surface (headings, links)
#125244 #FAFAF7 - AA Normal text
- Pass
- AA Large text
- Pass
- AAA Normal text
- Pass
- AAA Large text
- Pass
Body text on surface
#1A1A18 #FAFAF7 - AA Normal text
- Pass
- AA Large text
- Pass
- AAA Normal text
- Pass
- AAA Large text
- Pass
Secondary text (captions, metadata)
#4A4A46 #FAFAF7 - AA Normal text
- Pass
- AA Large text
- Pass
- AAA Normal text
- Pass
- AAA Large text
- Pass
Accent text (CTAs, important callouts)
#8B4010 #FAFAF7 - AA Normal text
- Pass
- AA Large text
- Pass
- AAA Normal text
- Pass
- AAA Large text
- Pass
What colour contrast does NOT cover
WCAG contrast ratios apply to text and UI components (form borders, icons). They do not apply to:
- Purely decorative images or backgrounds
- Logos and brand marks (though these are good practice to keep legible)
- Disabled state controls — WCAG explicitly excludes inactive UI components from contrast requirements
However, even where WCAG makes exceptions, considering contrast for decorative elements improves the experience for users with low vision and in challenging lighting conditions.
Colour alone is never sufficient
Colour must not be the only visual means of conveying information, indicating an action, or distinguishing a visual element (WCAG 1.4.1, Level A).
| Bad pattern | Accessible alternative |
|---|---|
| Required fields are red, optional are grey | Add an asterisk (*) and text label “Required” |
| Error state shown only by red border | Add an error icon + error message text |
| Selected tab shown by colour change only | Add underline, weight, or border to selected state |
| Chart series distinguished by colour only | Add patterns, shapes, or direct labels |
| ”Green means success, red means error” | Add checkmark/× icon + text label |
Dark mode
Dark mode is a user preference that must be respected. Some users require dark mode for health reasons (migraines, light sensitivity, vestibular disorders). Others prefer it for comfort.
- Provide a
@media (prefers-color-scheme: dark)implementation - Verify contrast ratios in both light and dark modes — they are different calculations
- Avoid pure white (#FFFFFF) text on pure black (#000000) backgrounds in dark mode — very high contrast (21:1) can cause halation (glowing) for some users; use near-white on near-black (e.g., #F0F0ED on #1A1A18)
- Respect the user’s OS preference as the default; an optional manual toggle is a bonus
Tools for checking contrast
- WebAIM Contrast Checker — webaim.org/resources/contrastchecker — fast, handles hex/RGB input
- Colour Contrast Analyser (TPGi) — free desktop app, eyedropper tool for checking any pixel on screen
- Chrome DevTools — hover any CSS colour in the Styles panel to see its contrast ratio in context
- Figma plugins: Stark, Contrast, Able — check contrast during design without leaving Figma
Text resize and zoom
Developer DesignerMany users with low vision set their browser to use a larger default font size (typically 20px or 24px instead of 16px), or zoom the page to 200% or 400%.
Requirements
| Criterion | Level | Requirement |
|---|---|---|
| 1.4.4 Resize Text | AA | Text must be resizable up to 200% without loss of content or functionality |
| 1.4.10 Reflow | AA | Content must reflow into a single column at 320px viewport width (equivalent to 400% zoom on a 1280px screen) without requiring horizontal scrolling |
How to design for resize
Use relative units, not pixels, for typography:
/* Bad: fixed pixel sizes break when user sets larger default */
body { font-size: 16px; }
h1 { font-size: 32px; }
/* Good: relative to user's browser default */
body { font-size: 1rem; } /* respects user preference */
h1 { font-size: 2rem; } /* scales with body */
p { font-size: 1rem; }
small { font-size: 0.875rem; }
Design for reflow at 320px viewport:
- All content must be readable in a single column at 320px
- No horizontal scrolling at 320px (except for data tables, code blocks, or images that cannot meaningfully wrap)
- Test by zooming your browser to 400% on a 1280px-wide screen — this simulates 320px effective viewport
Avoid fixed-height containers:
/* Bad: text overflows when user zooms */
.card { height: 120px; overflow: hidden; }
/* Good: expand to fit content */
.card { min-height: 120px; }
Avoid truncating text in critical UI:
- Navigation labels must not be clipped at larger text sizes
- Button text must wrap or the button must expand — never clip
- Form labels must remain associated with their inputs at all sizes
Spacing requirements (WCAG 1.4.12)
Users may override line height, letter spacing, and word spacing via browser extensions. Your layout must not break when these styles are applied:
- Line height to at least 1.5× the font size
- Spacing following paragraphs to at least 2× the font size
- Letter spacing to at least 0.12× the font size
- Word spacing to at least 0.16× the font size
To test: use the Text Spacing Bookmarklet from the WCAG 1.4.12 Understanding page, which injects these values into the current page.
Focus states
Designer DeveloperThe focus indicator is the visual marker that shows which interactive element currently has keyboard focus. It is the steering wheel for anyone navigating without a mouse.
Focus design requirements
| Criterion | Level | Requirement |
|---|---|---|
| 2.4.7 Focus Visible | AA | Keyboard focus indicator is visible |
| 2.4.11 Focus Appearance (Minimum) | AA (WCAG 2.2) | Focus indicator has at least 2px perimeter, 3:1 contrast with adjacent colours |
| 2.4.12 Focus Appearance | AAA (WCAG 2.2) | Focus indicator has at least 3px perimeter or fully enclosed |
Designing an effective focus indicator
A focus indicator should be:
- Visible against all backgrounds it will appear on — check contrast in all contexts
- At least 2px wide (3px+ for AAA)
- High-contrast — 3:1 minimum against the adjacent background colour
- Consistent — same style across all interactive elements (or a coherent system)
- Not layout-shifting — do not use margin or padding changes that move surrounding content
/* Example: consistent focus ring using box-shadow */
:focus-visible {
outline: 3px solid #C4611A;
outline-offset: 2px;
}
/* Remove outline only for pointer users (mouse/touch) */
:focus:not(:focus-visible) {
outline: none;
}
Note: :focus-visible is now well-supported and is the correct approach — it shows focus rings for keyboard users but not for mouse/touch users (who have their own visual feedback from hovering and pressing).
Focus order
The order in which focus moves through the page must be logical and predictable (WCAG 2.4.3, Level A):
- Focus order should follow the visual reading order (top to bottom, left to right in LTR content)
- Avoid using
tabindexvalues greater than 0 — they create a custom focus order that breaks the default DOM order - Use
tabindex="0"to make a non-interactive element focusable (e.g., a custom widget) - Use
tabindex="-1"to make an element programmatically focusable without adding it to the Tab sequence
<!-- Bad: positive tabindex creates unpredictable order -->
<button tabindex="3">Third</button>
<button tabindex="1">First</button>
<button tabindex="2">Second</button>
<!-- Good: follow DOM order -->
<button>First</button>
<button>Second</button>
<button>Third</button>
Focus trap management
Modal dialogs must trap focus — Tab should cycle within the dialog, not escape to the page behind:
// Focus trap for a modal dialog
function trapFocus(dialog) {
const focusable = dialog.querySelectorAll(
'a[href], button:not([disabled]), input, select, textarea, [tabindex="0"]'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
dialog.addEventListener('keydown', (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey) {
if (document.activeElement === first) {
e.preventDefault();
last.focus();
}
} else {
if (document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
});
}
Responsive and adaptive design
Designer DeveloperResponsive design — layouts that adapt to different viewport widths — is accessibility. Many users with low vision zoom browsers to 200–400%, effectively reducing the available viewport width. Mobile-only users (which disproportionately includes people with lower incomes or in remote areas) need the same access as desktop users.
Breakpoint principles
- Design mobile-first: start with the smallest viewport, enhance for larger
- Never require a minimum viewport width for basic functionality
- Avoid fixed-width containers that cause horizontal scrolling at small sizes
- Ensure tap targets are at least 44×44px on touch screens
Orientation
Do not lock orientation to landscape or portrait (WCAG 1.3.4, Level AA). Some users mount their devices in a fixed position due to motor disabilities — forcing orientation change is inaccessible to them.
/* Bad: forces landscape */
@media (orientation: portrait) {
body { display: none; }
}
/* Good: design works in both orientations */
Touch and pointer accessibility
- Touch targets: 44×44px minimum (WCAG 2.5.5 AAA); 24×24px minimum (WCAG 2.5.8 AA in WCAG 2.2)
- Spacing between adjacent targets: at least 8px to avoid accidental activation
- All touch gestures must have pointer-based alternatives: no swipe-only functionality
- Hover interactions must be activatable by touch or keyboard: no hover-only tooltips, no hover-only menus
Content reflow at 320px
Test your layouts at 320px viewport width. In Chrome DevTools: toggle device toolbar (Ctrl+Shift+M), set to 320px.
Common failures:
- Fixed-width tables: wrap in a horizontally scrollable container
- Wide code blocks: use
overflow-x: autoon<pre>elements - Side-by-side layouts that do not stack: switch to single-column at narrow widths
- Navigation that overflows: implement a hamburger/drawer pattern below a breakpoint
Motion safety
DesignerMotion on the web — animations, transitions, parallax, and auto-advancing content — is a risk for users with vestibular disorders and photosensitive epilepsy. The key principle is: design the base experience without motion, then add motion as an enhancement for users who have not requested reduced motion.
prefers-reduced-motion
The prefers-reduced-motion media query reads the user’s OS accessibility preference. Always implement it:
/* Motion-first approach: no motion by default */
.card {
transition: none;
}
/* Add motion only when the user has not requested reduced motion */
@media (prefers-reduced-motion: no-preference) {
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
}
What to remove under prefers-reduced-motion
| Effect | Action under prefers-reduced-motion |
|---|---|
| Parallax scrolling | Disable entirely — background becomes fixed |
| Page transition animations | Replace with instant change or simple opacity fade |
| Auto-advancing carousels | Pause auto-advance; user controls only |
| Large CSS animations | Remove or reduce to opacity-only |
| Scroll-triggered animations | Remove — show elements in final state |
| Video backgrounds | Replace with static image |
| Animated loading spinners | Replace with static indicator |
Safe motion
Not all animation is vestibular-triggering. Motion that is:
- Small area (button hover highlight, small spinner)
- Slow (0.3s or less transition)
- Non-spatial (opacity, colour, shadow — not position or scale changes)
…is generally safe even without the reduced-motion preference set. Spatial motion (position, scale, rotation) at high speed or over large areas is the primary risk.
Flashing content
Flashing content between 3–50 Hz can trigger photosensitive epileptic seizures (WCAG 2.3.1, Level A). This applies to:
- Animated GIFs with fast cycling frames
- JavaScript-driven blinking effects
- Videos with flash sequences
- Loading spinners that pulse rapidly
If in doubt: do not flash. A 1-second pulse (1 Hz) is safe. A rapidly blinking badge counter or loading bar is not. Use the Photosensitive Epilepsy Analysis Tool (PEAT) to analyze video content before publishing.
Typography
DesignerType settings for accessibility
| Property | Recommended value | Reason |
|---|---|---|
| Body font size | 16px (1rem) minimum | Many users have default font size set; do not go smaller |
| Line height | 1.5–1.6 for body text | Readability, especially for dyslexic readers |
| Paragraph spacing | At least 1.5× line height | Visual separation between ideas |
| Maximum line length | 60–80 characters (use max-width: 72ch) | Tracking ease; prevents eye strain on wide lines |
| Letter spacing | Do not tighten below font default | Tight tracking reduces legibility |
| Text alignment | Left-aligned (LTR) — not justified | Justified text creates uneven word spacing |
| All caps | Avoid for long passages | All-caps slows reading speed significantly |
Font selection
- Any legible, system or web font is acceptable — accessibility is achieved through contrast, size, and spacing, not font choice alone
- Do not use CSS
font-sizeinpxon the<html>or<body>element — useremor inherit the user’s browser default - Web fonts add loading cost — ensure font loading does not create a Flash of Invisible Text (FOIT); use
font-display: swap - Do not override user font preferences with
!important— some users (dyslexic readers) set their own preferred font via browser extensions
Iconography
- Icons used without text labels must have an accessible name:
aria-labelor<title>on SVG - Icon fonts (Font Awesome, etc.) require aria-hidden on the icon element and separate screen-reader-only text for the label
- Prefer SVG icons with
role="img"and<title>over icon fonts — more reliable across screen readers
<!-- Icon button with accessible name -->
<button aria-label="Close dialog">
<svg aria-hidden="true" focusable="false" width="24" height="24">
<!-- close icon paths -->
</svg>
</button>
<!-- Standalone icon with meaning -->
<svg role="img" aria-label="Warning" width="24" height="24">
<title>Warning</title>
<!-- icon paths -->
</svg>
<!-- Decorative icon — hidden from screen readers -->
<svg aria-hidden="true" focusable="false" width="24" height="24">
<!-- decorative icon paths -->
</svg>
Spacing and layout
White space
Adequate white space (padding, margins, gaps) is not just aesthetic — it reduces cognitive load, helps users identify related content groups, and makes touch targets easier to activate.
- Between interactive elements: at least 8px gap to prevent accidental activation
- Around body text: comfortable margins and padding reduce reading fatigue
- Between sections: clear visual separation through whitespace is more accessible than decorative dividers
Cards and content containers
- Avoid placing interactive elements so close together that they overlap in touch targets
- Avoid using borders alone to distinguish card content — provide adequate padding
- Ensure the clickable area of a card matches the visible card area (not just the heading link)
Design tokens and systems
Designer DeveloperA design system with documented accessibility constraints prevents accessibility debt from accumulating:
- Document the minimum contrast ratio approved for each colour pairing in the token system
- Include dark mode equivalents for all colour tokens
- Codify minimum touch target sizes in component specifications
- Define the standard focus ring style as a reusable component or CSS custom property
- Mark which animations respect prefers-reduced-motion in component documentation
/* Design tokens as CSS custom properties */
:root {
--color-primary: #125244;
--color-primary-light: #E8F5F1;
--color-text: #1A1A18;
--color-text-secondary: #4A4A46;
--color-surface: #FAFAF7;
--color-focus-ring: #C4611A;
--focus-ring: 3px solid var(--color-focus-ring);
--focus-ring-offset: 2px;
--transition-default: transform 0.2s ease, box-shadow 0.2s ease;
}
@media (prefers-color-scheme: dark) {
:root {
--color-primary: #7DC4B0;
--color-text: #EDEDE9;
--color-surface: #1A1A18;
/* ... all tokens re-specified for dark mode */
}
}
@media (prefers-reduced-motion: reduce) {
:root {
--transition-default: none;
}
}
Related pages
- Vision Disabilities — contrast and magnification needs for low vision users
- Neurological Disabilities — motion safety and vestibular considerations
- Mobility and Dexterity Disabilities — focus state design and touch target sizing
- Cognitive and Learning Disabilities — typography and cognitive load reduction
- Development Practices — implementing these principles in code