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

Designer

Colour 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

TargetText sizeMinimum ratio
WCAG AA — normal textUnder 18pt (24px) regular, or 14pt (18.67px) bold4.5:1
WCAG AA — large text18pt+ (24px+) regular, or 14pt+ (18.67px+) bold3:1
WCAG AA — UI componentsBorders of inputs, icons, focus rings3:1
WCAG AAA — normal textUnder 18pt regular, or 14pt bold7:1
WCAG AAA — large text18pt+ regular, or 14pt+ bold4.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
8.67:1 AAA Pass
AA Normal text
Pass
AA Large text
Pass
AAA Normal text
Pass
AAA Large text
Pass

Body text on surface

#1A1A18 #FAFAF7
16.67:1 AAA Pass
AA Normal text
Pass
AA Large text
Pass
AAA Normal text
Pass
AAA Large text
Pass

Secondary text (captions, metadata)

#4A4A46 #FAFAF7
8.51:1 AAA Pass
AA Normal text
Pass
AA Large text
Pass
AAA Normal text
Pass
AAA Large text
Pass

Accent text (CTAs, important callouts)

#8B4010 #FAFAF7
7.07:1 AAA Pass
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 patternAccessible alternative
Required fields are red, optional are greyAdd an asterisk (*) and text label “Required”
Error state shown only by red borderAdd an error icon + error message text
Selected tab shown by colour change onlyAdd underline, weight, or border to selected state
Chart series distinguished by colour onlyAdd 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 Designer

Many 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

CriterionLevelRequirement
1.4.4 Resize TextAAText must be resizable up to 200% without loss of content or functionality
1.4.10 ReflowAAContent 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 Developer

The 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

CriterionLevelRequirement
2.4.7 Focus VisibleAAKeyboard 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 AppearanceAAA (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 tabindex values 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 Developer

Responsive 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: auto on <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

Designer

Motion 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

EffectAction under prefers-reduced-motion
Parallax scrollingDisable entirely — background becomes fixed
Page transition animationsReplace with instant change or simple opacity fade
Auto-advancing carouselsPause auto-advance; user controls only
Large CSS animationsRemove or reduce to opacity-only
Scroll-triggered animationsRemove — show elements in final state
Video backgroundsReplace with static image
Animated loading spinnersReplace 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

Designer

Type settings for accessibility

PropertyRecommended valueReason
Body font size16px (1rem) minimumMany users have default font size set; do not go smaller
Line height1.5–1.6 for body textReadability, especially for dyslexic readers
Paragraph spacingAt least 1.5× line heightVisual separation between ideas
Maximum line length60–80 characters (use max-width: 72ch)Tracking ease; prevents eye strain on wide lines
Letter spacingDo not tighten below font defaultTight tracking reduces legibility
Text alignmentLeft-aligned (LTR) — not justifiedJustified text creates uneven word spacing
All capsAvoid for long passagesAll-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-size in px on the <html> or <body> element — use rem or 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-label or <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 Developer

A 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;
  }
}