wcag-design

Create accessible, production-grade frontend interfaces that conform to WCAG 2.2 AA standards while maintaining high design quality. Use this skill when building any web component, page, dashboard, or application where accessibility compliance (WCAG, ADA, Section 508, EN 301 549) is required or desired, or when auditing/fixing accessibility issues in existing UI code. Combines comprehensive W3C WAI accessibility knowledge with distinctive frontend design principles.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "wcag-design" with this command: npx skills add alexsnchz/skills/alexsnchz-skills-wcag-design

WCAG-Design Skill

This skill guides the creation of accessible, visually distinctive frontend interfaces that conform to WCAG 2.2 AA standards. Accessibility and design quality are not opposing forces — the best accessible interfaces are also beautifully designed.


Part 1 — WCAG Fundamentals

What is WCAG?

The Web Content Accessibility Guidelines (WCAG) is the global standard for web accessibility, published by the W3C through the Web Accessibility Initiative (WAI). WCAG defines how to make web content accessible to people with disabilities including blindness, low vision, deafness, hearing loss, limited movement, photosensitivity, speech disabilities, and cognitive/learning disabilities.

Version History

VersionYearNotes
WCAG 1.01999First W3C accessibility recommendation
WCAG 2.02008Major restructure with testable success criteria
WCAG 2.12018+17 new criteria for mobile, low vision, cognitive
WCAG 2.22023+9 new criteria, removed 4.1.1 Parsing; current standard
WCAG 3.0~2028Draft; plain language, outcome-oriented model

Backward compatibility: Content conforming to WCAG 2.2 also conforms to 2.1 and 2.0.


Part 2 — The Four POUR Principles

Every WCAG success criterion maps to one of four principles:

1. Perceivable (P)

All information and UI components must be presentable in ways users can perceive.

  • Text alternatives for non-text content
  • Captions and audio descriptions for media
  • Adaptable content presentation
  • Sufficient color contrast and readability

2. Operable (O)

All UI components and navigation must be operable.

  • Full keyboard accessibility
  • No keyboard traps
  • Sufficient time for interactions
  • No seizure-inducing content
  • Multiple input modalities (keyboard, touch, voice)
  • Accessible focus management

3. Understandable (U)

Users must understand the content and how the interface works.

  • Readable and understandable text
  • Predictable, consistent behavior
  • Input assistance and error recovery
  • Clear language and definitions

4. Robust (R)

Content must work reliably with current and future technologies, especially assistive technologies.

  • Valid, well-formed markup
  • Compatible with browsers and assistive technologies
  • Proper semantic structure

Part 3 — Conformance Levels

LevelCriteria CountDescription
A32Minimum — basic accessibility, foundational compliance
AA+24 (56 total)Recommended — most legal requirements; addresses common barriers
AAA+31 (87 total)Enhanced — highest level; not always required

Target: WCAG 2.2 AA — This is the level required by most accessibility laws (ADA, EN 301 549, Section 508, Accessibility for Ontarians with Disabilities Act, etc.).


Part 4 — Complete WCAG 2.2 Success Criteria Reference

PRINCIPLE 1: PERCEIVABLE

Guideline 1.1 — Text Alternatives

CriterionLevelRule
1.1.1 Non-text ContentAAll non-text content has a text alternative (images, icons, charts, buttons)

Guideline 1.2 — Time-based Media

CriterionLevelRule
1.2.1 Audio-only and Video-only (Prerecorded)AProvide transcript or audio description
1.2.2 Captions (Prerecorded)ACaptions for all prerecorded audio in multimedia
1.2.3 Audio Description or Media Alternative (Prerecorded)AProvide audio description or full text alternative
1.2.4 Captions (Live)AACaptions for all live multimedia
1.2.5 Audio Description (Prerecorded)AAAudio description for all prerecorded video
1.2.6 Sign Language (Prerecorded)AAASign language interpretation for prerecorded audio
1.2.7 Extended Audio Description (Prerecorded)AAAExtended audio description where pauses are insufficient
1.2.8 Media Alternative (Prerecorded)AAAFull text alternative for all prerecorded media
1.2.9 Audio-only (Live)AAAAlternative for all live audio-only content

Guideline 1.3 — Adaptable

CriterionLevelRule
1.3.1 Info and RelationshipsAStructure and relationships conveyed through presentation must be programmatically determinable
1.3.2 Meaningful SequenceAReading and navigation order is logical
1.3.3 Sensory CharacteristicsAInstructions don't rely solely on color, size, shape, visual location, or sound
1.3.4 OrientationAAContent not restricted to portrait or landscape orientation
1.3.5 Identify Input PurposeAAInput fields have programmatically identified purpose (autocomplete)
1.3.6 Identify PurposeAAAPurpose of buttons, links, and controls can be programmatically determined

Guideline 1.4 — Distinguishable

CriterionLevelRule
1.4.1 Use of ColorAColor is not the only visual means of conveying information
1.4.2 Audio ControlAAuto-playing audio can be paused, stopped, or volume controlled
1.4.3 Contrast (Minimum)AA4.5:1 for normal text; 3:1 for large text
1.4.4 Resize TextAAText can be resized to 200% without loss of content
1.4.5 Images of TextAAUse actual text instead of images of text where possible
1.4.6 Contrast (Enhanced)AAA7:1 for normal text; 4.5:1 for large text
1.4.7 Low or No Background AudioAAAAudio-only content clearly audible over background
1.4.8 Visual PresentationAAAText width limited, line spacing adjustable, no full justification
1.4.9 Images of Text (No Exception)AAANo images of text except logos
1.4.10 ReflowAAContent presentable without horizontal scrolling at 320px wide
1.4.11 Non-text ContrastAAUI components and graphical elements have 3:1 contrast ratio
1.4.12 Text SpacingAANo loss of content when line height ≥1.5×, spacing ≥0.12em, word spacing ≥0.16em
1.4.13 Content on Hover or FocusAAHoverable/focusable content is dismissible, hoverable, and persistent

PRINCIPLE 2: OPERABLE

Guideline 2.1 — Keyboard Accessible

CriterionLevelRule
2.1.1 KeyboardAAll functionality operable via keyboard
2.1.2 No Keyboard TrapAUser can navigate away from any component using keyboard
2.1.3 Keyboard (No Exception)AAAAll functionality operable via keyboard — no exceptions
2.1.4 Character Key ShortcutsASingle-character shortcuts can be disabled or remapped

Guideline 2.2 — Enough Time

CriterionLevelRule
2.2.1 Timing AdjustableATime limits can be turned off, adjusted, or extended (≥10× default)
2.2.2 Pause, Stop, HideAAuto-moving/blinking/scrolling content can be paused, stopped, or hidden
2.2.3 No TimingAAANo time restrictions except for real-time events
2.2.4 InterruptionsAAAUser can postpone or suppress all interruptions
2.2.5 Re-authenticatingAAANo data lost when re-authenticating after timeout
2.2.6 TimeoutsAAAUsers warned about session inactivity timeout

Guideline 2.3 — Seizures and Physical Reactions

CriterionLevelRule
2.3.1 Three Flashes or Below ThresholdANo flashing content more than 3 times/second (or below threshold)
2.3.2 Three FlashesAAAAll content restricted to ≤3 flashes/second
2.3.3 Animation from InteractionsAAAMotion animation triggered by interaction can be disabled

Guideline 2.4 — Navigable

CriterionLevelRule
2.4.1 Bypass BlocksAMechanism to skip repeated blocks (skip nav link)
2.4.2 Page TitledAWeb pages have descriptive titles
2.4.3 Focus OrderAFocus order is logical and meaningful
2.4.4 Link Purpose (In Context)ALink purpose clear from link text or context
2.4.5 Multiple WaysAAMultiple ways to locate a page (search, sitemap, navigation)
2.4.6 Headings and LabelsAAHeadings and labels describe topic or purpose
2.4.7 Focus VisibleAAKeyboard focus indicator is visible
2.4.8 LocationAAAUser knows their location within a set of pages
2.4.9 Link Purpose (Link Only)AAALink purpose clear from link text alone
2.4.10 Section HeadingsAAASection headings organize content
2.4.11 Focus Not Obscured (Minimum) ⭐ NEW 2.2AAFocused element not entirely hidden by sticky/fixed content
2.4.12 Focus Not Obscured (Enhanced) ⭐ NEW 2.2AAAFocused element fully visible
2.4.13 Focus Appearance ⭐ NEW 2.2AAAFocus indicator: ≥2px outline, ≥3:1 contrast change

Guideline 2.5 — Input Modalities

CriterionLevelRule
2.5.1 Pointer GesturesAPath-based gestures have single-pointer alternative
2.5.2 Pointer CancellationAPointer actions activate on "up" event, not "down"
2.5.3 Label in NameAAccessible name contains the visible label text
2.5.4 Motion ActuationAMotion-triggered functions have UI alternative and can be disabled
2.5.5 Target Size (Enhanced)AAATouch targets ≥44×44 CSS pixels
2.5.6 Concurrent Input MechanismsAAANo restriction on input modality
2.5.7 Dragging Movements ⭐ NEW 2.2AADrag-only functionality has single-pointer alternative
2.5.8 Target Size (Minimum) ⭐ NEW 2.2AATouch targets ≥24×24 CSS pixels or adequate spacing

PRINCIPLE 3: UNDERSTANDABLE

Guideline 3.1 — Readable

CriterionLevelRule
3.1.1 Language of PageADefault human language programmatically identified (lang attribute)
3.1.2 Language of PartsAALanguage changes in content are marked
3.1.3 Unusual WordsAAADefinitions for jargon, idioms, technical terms
3.1.4 AbbreviationsAAAAbbreviations expanded on first use
3.1.5 Reading LevelAAAText at lower secondary education level or simplified version available
3.1.6 PronunciationAAAPronunciation provided for ambiguous words

Guideline 3.2 — Predictable

CriterionLevelRule
3.2.1 On FocusANo context change when component receives focus
3.2.2 On InputAChanging settings doesn't automatically submit form
3.2.3 Consistent NavigationAANavigation mechanisms in same relative order across pages
3.2.4 Consistent IdentificationAAComponents with same function identified consistently
3.2.5 Change on RequestAAAContext changes only at user request
3.2.6 Consistent Help ⭐ NEW 2.2AHelp mechanisms in same location across pages

Guideline 3.3 — Input Assistance

CriterionLevelRule
3.3.1 Error IdentificationAErrors identified and described in text
3.3.2 Labels or InstructionsALabels or instructions provided for all inputs
3.3.3 Error SuggestionAASuggestions provided for input errors where known
3.3.4 Error Prevention (Legal, Financial, Data)AACritical submissions are reversible, checked, or confirmed
3.3.5 HelpAAAContext-sensitive help available
3.3.6 Error Prevention (All)AAAAll inputs can be reversed, checked, or confirmed
3.3.7 Redundant Entry ⭐ NEW 2.2APreviously entered info not required again in same session
3.3.8 Accessible Authentication (Minimum) ⭐ NEW 2.2AAAuthentication without cognitive function test or memory requirement
3.3.9 Accessible Authentication (Enhanced) ⭐ NEW 2.2AAAEnhanced alternatives to cognitive function tests

PRINCIPLE 4: ROBUST

Guideline 4.1 — Compatible

CriterionLevelRule
4.1.1 ParsingARemoved in WCAG 2.2 — obsolete due to HTML5 error correction
4.1.2 Name, Role, ValueAAll components have accessible name, role, and state/value
4.1.3 Status MessagesAAStatus messages announced to screen readers without focus change

Part 5 — Implementation Reference

5.1 Color Contrast Rules

ContextAA MinimumAAA Enhanced
Normal text (<18px or <14px bold)4.5:17:1
Large text (≥18px or ≥14px bold)3:14.5:1
UI components & graphics3:13:1
Placeholder text4.5:1
Disabled elementsExemptExempt
Decorative elementsExemptExempt

Dark Mode Note: Dark themes must still meet contrast ratios. Avoid pure black #000000 (causes halation/eye strain); use soft blacks like #0f0f0f or #121212. High-saturation colors on dark backgrounds may vibrate visually — reduce saturation.

Tools: WebAIM Contrast Checker, Colour Contrast Analyser, axe DevTools, Chrome DevTools.


5.2 Semantic HTML — Use Native Elements First

Always prefer semantic HTML over ARIA. ARIA is a supplement, not a replacement.

<!-- ✅ CORRECT: Semantic HTML -->
<header>…</header>
<nav aria-label="Main navigation">…</nav>
<main>
  <article>…</article>
  <aside>…</aside>
</main>
<footer>…</footer>

<!-- ❌ WRONG: div soup -->
<div class="header">…</div>
<div class="nav">…</div>

Semantic element → landmark mapping:

ElementARIA Landmark Role
<header>banner
<nav>navigation
<main>main
<aside>complementary
<footer>contentinfo
<section aria-label="...">region
<form>form

Heading hierarchy rule: Never skip levels. <h1><h2><h3> (not h1 → h3). Multiple <h1> elements are acceptable but first should be page/section title.


5.3 WAI-ARIA Quick Reference

Use ARIA only when semantic HTML is insufficient. The First Rule of ARIA: don't use ARIA if you can use a native HTML element or attribute with the semantics built in.

Landmark Roles

<div role="banner">…</div>        <!-- Same as <header> -->
<div role="navigation">…</div>    <!-- Same as <nav> -->
<div role="main">…</div>          <!-- Same as <main> -->
<div role="complementary">…</div> <!-- Same as <aside> -->
<div role="contentinfo">…</div>   <!-- Same as <footer> -->
<div role="search">…</div>        <!-- Search region -->
<div role="region" aria-label="…">…</div> <!-- Generic named region -->

Widget Roles (Interactive Components)

role="button"      <!-- Clickable element -->
role="checkbox"    <!-- Checkable input -->
role="radio"       <!-- Radio button -->
role="combobox"    <!-- Dropdown / autocomplete -->
role="listbox"     <!-- List for selection -->
role="option"      <!-- Item within listbox -->
role="menu"        <!-- Menu container -->
role="menuitem"    <!-- Menu item -->
role="tab"         <!-- Tab button -->
role="tablist"     <!-- Tab container -->
role="tabpanel"    <!-- Tab content panel -->
role="dialog"      <!-- Modal dialog -->
role="alertdialog" <!-- Alert modal -->
role="slider"      <!-- Range input -->
role="progressbar" <!-- Progress indicator -->
role="tooltip"     <!-- Tooltip -->
role="tree"        <!-- Tree widget -->
role="treeitem"    <!-- Tree item -->
role="grid"        <!-- Interactive data grid -->
role="gridcell"    <!-- Grid cell -->

Live Region Roles (Dynamic Content)

role="alert"       <!-- Assertive: important time-sensitive info -->
role="status"      <!-- Polite: advisory info (status messages) -->
role="log"         <!-- Polite: ordered sequence of updates -->
role="timer"       <!-- Timer announcements -->

<!-- Equivalent ARIA live properties -->
aria-live="polite"    <!-- Announces after current speech -->
aria-live="assertive" <!-- Interrupts current speech (use sparingly) -->
aria-live="off"       <!-- No announcements (default) -->
aria-atomic="true"    <!-- Announce entire region on update -->
aria-relevant="additions text" <!-- What triggers announcement -->

Essential ARIA Properties

<!-- Labeling -->
aria-label="Close dialog"
aria-labelledby="heading-id"
aria-describedby="description-id help-text-id"

<!-- State -->
aria-expanded="true|false"       <!-- Collapsible element open state -->
aria-selected="true|false"       <!-- Selection state -->
aria-checked="true|false|mixed"  <!-- Checkbox/switch state -->
aria-pressed="true|false|mixed"  <!-- Toggle button state -->
aria-disabled="true"             <!-- Disabled state -->
aria-hidden="true"               <!-- Hidden from AT -->
aria-invalid="true|false|grammar|spelling" <!-- Validation state -->
aria-busy="true|false"           <!-- Loading state -->

<!-- Relationships -->
aria-controls="panel-id"         <!-- Controls another element -->
aria-owns="list-id"              <!-- Owns child elements -->
aria-haspopup="true|menu|listbox|tree|grid|dialog" <!-- Has popup -->
aria-autocomplete="none|inline|list|both"  <!-- Autocomplete behavior -->

<!-- Range -->
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
aria-valuetext="50 percent"      <!-- Human-readable value -->

<!-- Current location -->
aria-current="page|step|location|date|time|true|false"

5.4 Keyboard Navigation Implementation

Every interactive component must be fully keyboard-accessible.

Focus Management Rules

/* NEVER do this without a custom replacement */
*:focus { outline: none; }

/* ✅ Custom focus indicator — meets WCAG 2.4.7 and 2.4.13 */
:focus-visible {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
  border-radius: 2px;
}

/* Different styles for mouse vs keyboard */
:focus:not(:focus-visible) { outline: none; }
:focus-visible { outline: 2px solid #005fcc; outline-offset: 2px; }

Tab Order

<!-- ✅ Natural DOM order (preferred) -->
<!-- ✅ Use tabindex="0" to add element to tab order -->
<div role="button" tabindex="0">Custom Button</div>

<!-- ❌ NEVER use positive tabindex (breaks natural order) -->
<button tabindex="3">Bad</button>

<!-- Remove from tab order (but still scriptable) -->
<div tabindex="-1" id="modal-content">…</div>

Skip Navigation Link

<!-- First element in <body> — visible on focus -->
<a href="#main-content" class="skip-link">Skip to main content</a>

<style>
.skip-link {
  position: absolute;
  top: -100%;
  left: 0;
  padding: 0.5rem 1rem;
  background: #000;
  color: #fff;
  z-index: 9999;
  transition: top 0.1s;
}
.skip-link:focus { top: 0; }
</style>

<main id="main-content">…</main>

Keyboard Patterns by Component

ComponentKeyAction
AllTabMove focus forward
AllShift+TabMove focus backward
ButtonEnter / SpaceActivate
LinkEnterFollow link
CheckboxSpaceToggle
Radio GroupArrow KeysSelect option
Select/DropdownArrow KeysNavigate; Enter select
DialogEscapeClose
DialogTabCycle within dialog only
MenuArrow KeysNavigate items
MenuEscapeClose
MenuEnter / SpaceActivate item
SliderArrow KeysIncrement/Decrement
SliderHome / EndMin / Max value
TabsArrow KeysSwitch tabs
TreeArrow KeysExpand/collapse/navigate

Focus Trap for Modals

// Trap focus within modal while open
function trapFocus(modal) {
  const focusable = modal.querySelectorAll(
    'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'
  );
  const firstEl = focusable[0];
  const lastEl = focusable[focusable.length - 1];

  modal.addEventListener('keydown', (e) => {
    if (e.key !== 'Tab') return;
    if (e.shiftKey) {
      if (document.activeElement === firstEl) {
        e.preventDefault(); lastEl.focus();
      }
    } else {
      if (document.activeElement === lastEl) {
        e.preventDefault(); firstEl.focus();
      }
    }
  });
}

5.5 Accessible Component Patterns

Modal / Dialog

<dialog
  role="dialog"
  aria-modal="true"
  aria-labelledby="modal-title"
  aria-describedby="modal-desc"
  id="my-modal"
>
  <h2 id="modal-title">Confirm Action</h2>
  <p id="modal-desc">Are you sure you want to delete this item?</p>
  <button type="button" autofocus>Cancel</button>
  <button type="button">Delete</button>
</dialog>
// HTML5 <dialog> — built-in focus management + Escape handling
const modal = document.getElementById('my-modal');
const trigger = document.getElementById('open-btn');

trigger.addEventListener('click', () => modal.showModal());
modal.addEventListener('close', () => trigger.focus()); // Restore focus

Accessible Button (Custom)

<!-- Prefer <button> whenever possible -->
<button type="button" aria-pressed="false" id="dark-toggle">
  <svg aria-hidden="true" focusable="false">…</svg>
  Dark Mode
</button>

<!-- Icon-only button: needs aria-label -->
<button type="button" aria-label="Close dialog">
  <svg aria-hidden="true" focusable="false">…</svg>
</button>

Accordion

<div class="accordion">
  <h3>
    <button
      type="button"
      aria-expanded="false"
      aria-controls="panel-1"
      id="btn-1"
    >Section Title</button>
  </h3>
  <div id="panel-1" role="region" aria-labelledby="btn-1" hidden>
    Panel content here
  </div>
</div>

Tabs

<div role="tablist" aria-label="Settings sections">
  <button role="tab" aria-selected="true" aria-controls="panel-a" id="tab-a" tabindex="0">General</button>
  <button role="tab" aria-selected="false" aria-controls="panel-b" id="tab-b" tabindex="-1">Privacy</button>
</div>

<div role="tabpanel" id="panel-a" aria-labelledby="tab-a">…</div>
<div role="tabpanel" id="panel-b" aria-labelledby="tab-b" hidden>…</div>

Combobox / Autocomplete

<label for="city-input">City</label>
<input
  type="text"
  id="city-input"
  role="combobox"
  aria-expanded="false"
  aria-haspopup="listbox"
  aria-autocomplete="list"
  aria-controls="city-list"
/>
<ul id="city-list" role="listbox" aria-label="Cities">
  <li role="option" id="opt-1">New York</li>
  <li role="option" id="opt-2">Los Angeles</li>
</ul>

Alert / Status Message

<!-- Polite announcement (most cases) -->
<div role="status" aria-live="polite" aria-atomic="true">
  Form saved successfully.
</div>

<!-- Assertive announcement (errors only) -->
<div role="alert" aria-live="assertive" aria-atomic="true">
  Error: Please enter a valid email address.
</div>

5.6 Forms — Accessible Patterns

<!-- ✅ Complete accessible form field -->
<div class="field">
  <label for="email">
    Email address
    <span aria-hidden="true" class="required">*</span>
    <span class="sr-only">(required)</span>
  </label>
  <input
    type="email"
    id="email"
    name="email"
    autocomplete="email"
    aria-required="true"
    aria-describedby="email-hint email-error"
    aria-invalid="false"
  />
  <p id="email-hint" class="hint">We'll never share your email.</p>
  <p id="email-error" class="error" role="alert" hidden>
    Please enter a valid email address.
  </p>
</div>

<!-- ✅ Grouped radio buttons -->
<fieldset>
  <legend>Notification preferences</legend>
  <label><input type="radio" name="notif" value="email"> Email</label>
  <label><input type="radio" name="notif" value="sms"> SMS</label>
  <label><input type="radio" name="notif" value="none"> None</label>
</fieldset>

Form validation rules:

  • Validate on blur or submit, not on every keystroke
  • Mark invalid fields with aria-invalid="true"
  • Link error messages to fields via aria-describedby
  • Use role="alert" on dynamically inserted error messages
  • Never use placeholder text as the only label
  • Provide autocomplete attributes for common fields

5.7 Images and Alt Text

<!-- ✅ Informative image -->
<img src="chart.png" alt="Sales by region 2024: North 35%, South 28%, East 22%, West 15%">

<!-- ✅ Decorative image — empty alt required -->
<img src="divider.png" alt="">

<!-- ✅ Icon button — label on button, not image -->
<button aria-label="Search">
  <img src="magnifier.svg" alt="">
</button>

<!-- ✅ Complex diagram — short alt + detailed description -->
<figure>
  <img src="architecture.png" alt="System architecture diagram" aria-describedby="arch-desc">
  <figcaption id="arch-desc">
    Three-tier architecture: client tier connects to API gateway,
    which routes to microservices cluster, backed by PostgreSQL and Redis.
  </figcaption>
</figure>

<!-- ✅ CSS background image (decorative) — no alt needed -->
<div class="hero-bg" role="img" aria-label="Abstract blue wave">…</div>
<!-- OR if purely decorative: aria-hidden="true" -->

Alt text guidelines:

  • Keep concise (≤125 characters typical; up to 250 for complex)
  • Don't start with "Image of" or "Picture of"
  • Describe purpose and content, not appearance alone
  • For charts: include key data points
  • For logos: use company name only

5.8 Typography Accessibility

/* ✅ Accessible base typography */
html {
  font-size: 100%; /* Don't fix to px — respects user browser setting */
}

body {
  font-size: 1rem;      /* = 16px by default */
  line-height: 1.5;     /* Readable line spacing */
  max-width: 75ch;      /* Limit line length for readability */
  font-family: /* Choose a readable, designed typeface */;
}

/* ✅ Allow text spacing override (WCAG 1.4.12) */
* {
  line-height: inherit !important;    /* only in text-spacing tests */
  letter-spacing: inherit !important;
  word-spacing: inherit !important;
}

/* ✅ Respect zoom — use relative units */
h1 { font-size: clamp(1.5rem, 4vw, 2.5rem); }
p  { font-size: clamp(1rem, 1.2vw, 1.125rem); }
GuidelineMinimumRecommended
Body text size14px (0.875rem)16px (1rem)
Line height1.41.5–1.6
Max line length65–75 characters
Letter spacing0 to +0.05em
Paragraph spacing1.5× font-size

Font selection for accessibility:

  • Choose typefaces with clear letterform differentiation (b/d/p/q distinction)
  • Avoid decorative fonts for body text
  • Avoid light weights (< 300) at small sizes
  • Ensure sufficient x-height for readability

5.9 Motion and Animation

/* ✅ Respect prefers-reduced-motion — implement always */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ✅ Progressive enhancement pattern */
.card { /* base — no motion */ }

@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: transform 0.2s ease, box-shadow 0.2s ease;
  }
  .card:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 24px rgba(0,0,0,0.12);
  }
}

Motion rules:

  • Never autoplay videos with motion — use prefers-reduced-motion
  • Flashing content: max 3 flashes/second (WCAG 2.3.1)
  • Parallax, full-page scroll jacking → provide prefers-reduced-motion: reduce alternative
  • Loading spinners are acceptable; full-page animated transitions are not without alternative

5.10 Responsive and Mobile Accessibility

/* ✅ WCAG 1.4.10 Reflow — no horizontal scroll at 320px */
@media (max-width: 320px) {
  .container { width: 100%; padding: 0 1rem; }
  table { display: block; overflow-x: auto; }
}

/* ✅ WCAG 2.5.8 Minimum target size — 24×24px with spacing */
button, a, [role="button"] {
  min-height: 44px;      /* Meet WCAG 2.5.5 AAA for comfort */
  min-width: 44px;
  padding: 0.5rem 1rem;
}

/* ✅ Ensure focus remains visible at all sizes */
:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}

Mobile-specific considerations:

  • Use appropriate type attributes: type="email", type="tel", type="number"
  • Don't disable zoom: <meta name="viewport" content="width=device-width, initial-scale=1"> — NEVER add user-scalable=no
  • Test with VoiceOver (iOS) and TalkBack (Android)
  • Avoid hover-only interactions (no hover on touch devices)
  • inputmode attribute for numeric, decimal, tel, email, url, search inputs

5.11 Tables

<!-- ✅ Accessible data table -->
<table>
  <caption>Q4 2024 Sales by Region</caption>
  <thead>
    <tr>
      <th scope="col">Region</th>
      <th scope="col">Sales (USD)</th>
      <th scope="col">Growth</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">North America</th>
      <td>$1,240,000</td>
      <td>+12%</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <th scope="row">Total</th>
      <td>$3,890,000</td>
      <td>+8%</td>
    </tr>
  </tfoot>
</table>

Rules:

  • Use <caption> to title every table
  • Use <th scope="col|row|colgroup|rowgroup"> for all header cells
  • Use <thead>, <tbody>, <tfoot> for structure
  • Don't use tables for layout; use CSS Grid/Flexbox instead

Part 6 — Design with Accessibility: Frontend Aesthetic Guidelines

6.1 Design Thinking for Accessible Interfaces

Before coding, commit to a bold, intentional aesthetic direction while planning for accessibility:

  • Purpose: What problem does this solve? Who uses it? What disabilities might users have?
  • Tone: Choose an aesthetic extreme: brutally minimal, maximalist typographic, organic/natural, editorial, brutalist, retro-futuristic, luxury/refined, playful, industrial, etc.
  • Constraints: Framework, performance, WCAG level (target AA), contrast requirements
  • Differentiation: What makes this unforgettable AND accessible? Bold design and WCAG are compatible.

Accessibility informs design, it doesn't constrain it. The best accessible designs use:

  • Strong color contrast as a design feature, not a liability
  • Clear typographic hierarchy as a visual system
  • Visible focus states as intentional design elements
  • Semantic structure as compositional logic

6.2 Color System Design for Accessibility

Design accessible color systems:

:root {
  /* Accessible palette using HSL for easier contrast calculation */
  --color-bg: hsl(220, 15%, 10%);        /* Near-black background */
  --color-surface: hsl(220, 12%, 15%);   /* Elevated surface */
  --color-border: hsl(220, 10%, 25%);    /* Subtle border */
  --color-text-primary: hsl(220, 10%, 95%);   /* High contrast text: >7:1 */
  --color-text-secondary: hsl(220, 8%, 70%);  /* Medium contrast: ~4.5:1 */
  --color-text-muted: hsl(220, 6%, 55%);      /* Low contrast: ~3:1 (large text only) */
  --color-accent: hsl(210, 100%, 65%);        /* Action color: verify contrast */
  --color-accent-hover: hsl(210, 100%, 72%);  /* Hover state */
  --color-error: hsl(0, 80%, 65%);            /* Error — verify 4.5:1 on bg */
  --color-success: hsl(145, 60%, 55%);        /* Success — verify 4.5:1 on bg */
  --color-warning: hsl(45, 90%, 55%);         /* Warning — verify 4.5:1 on bg */

  /* Focus ring — must contrast 3:1 with adjacent colors (WCAG 2.4.7) */
  --color-focus: hsl(210, 100%, 65%);
  --focus-ring: 0 0 0 3px var(--color-focus);
}

/* ✅ Color-scheme support */
@media (prefers-color-scheme: light) {
  :root {
    --color-bg: hsl(0, 0%, 99%);
    --color-text-primary: hsl(220, 20%, 8%);
    /* ... recalculate all contrasts */
  }
}

Multi-modal communication (never color alone):

/* ❌ Color only for error */
.input-error { border-color: red; }

/* ✅ Color + icon + text + pattern */
.input-error {
  border-color: var(--color-error);
  border-width: 2px;
  background-image: url("error-icon.svg");
  background-position: right 12px center;
  background-repeat: no-repeat;
  padding-right: 40px;
}

6.3 Typography Design System

/* ✅ Accessible, distinctive type scale */
:root {
  /* Scale: Major Third (1.25) or Perfect Fourth (1.333) */
  --text-xs:   0.75rem;    /* 12px — captions, labels */
  --text-sm:   0.875rem;   /* 14px — secondary text */
  --text-base: 1rem;       /* 16px — body */
  --text-lg:   1.125rem;   /* 18px — lead text */
  --text-xl:   1.25rem;    /* 20px */
  --text-2xl:  1.5rem;     /* 24px */
  --text-3xl:  1.875rem;   /* 30px */
  --text-4xl:  2.25rem;    /* 36px */
  --text-5xl:  3rem;       /* 48px */
  --text-6xl:  3.75rem;    /* 60px */

  /* Accessible line heights */
  --leading-tight:  1.25;
  --leading-snug:   1.375;
  --leading-normal: 1.5;
  --leading-relaxed:1.625;
  --leading-loose:  2;

  /* Weights */
  --font-light:    300;  /* Use sparingly, large sizes only */
  --font-normal:   400;
  --font-medium:   500;
  --font-semibold: 600;
  --font-bold:     700;
  --font-black:    900;
}

Font selection for accessible uniqueness:

  • Avoid Inter, Roboto, Arial, system-ui for display — too generic
  • Choose typefaces with strong optical size range: DM Sans, Fraunces, Bricolage Grotesque, Playfair Display, Syne, General Sans, Cabinet Grotesk, Instrument Serif
  • Pair distinctive display fonts with clean, readable body fonts
  • Verify chosen fonts render legibly at body sizes (14–18px)
  • Always have a readable fallback stack

6.4 Focus Indicator as Design Element

Make focus indicators intentional design features, not afterthoughts:

/* Option 1: Solid outline */
:focus-visible {
  outline: 3px solid var(--color-accent);
  outline-offset: 3px;
  border-radius: var(--radius-sm, 4px);
}

/* Option 2: Box shadow (inset for contained elements) */
:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--color-bg), 0 0 0 5px var(--color-accent);
}

/* Option 3: High visibility for text links */
a:focus-visible {
  outline: none;
  background-color: var(--color-accent);
  color: var(--color-bg);
  text-decoration: none;
  border-radius: 2px;
  padding: 0 2px;
}

/* Verify contrast: focused vs unfocused → must be ≥3:1 change */

6.5 Spacing and Layout for Motor Accessibility

/* ✅ Generous interactive targets */
.btn {
  /* Minimum visually 44×44 (WCAG 2.5.5 AAA) */
  min-height: 44px;
  padding-inline: 1.25rem;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

/* ✅ Inline links — guaranteed minimum height */
a {
  display: inline-block; /* Ensures tap area */
  padding: 0.25em 0;
}

/* ✅ Spacing between small targets (WCAG 2.5.8 AA) */
.icon-group { gap: 0.5rem; } /* 8px between 24px targets = sufficient */

/* ✅ Grid and flex for logical order */
/* Visual order must match DOM order for keyboard/screen reader users */
/* Avoid using CSS `order` property to reorder visual layout */

6.6 Accessible Animation Design

/* ✅ Always implement reduced-motion alternative */
:root {
  --duration-fast: 150ms;
  --duration-base: 250ms;
  --duration-slow: 400ms;
  --easing-out: cubic-bezier(0.16, 1, 0.3, 1);
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-fast: 0ms;
    --duration-base: 0ms;
    --duration-slow: 0ms;
  }
}

/* ✅ Purpose-driven micro-interactions */
.btn {
  transition:
    background-color var(--duration-fast) ease,
    transform var(--duration-fast) var(--easing-out),
    box-shadow var(--duration-fast) ease;
}

.btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}

.btn:active {
  transform: translateY(0);
  box-shadow: none;
}

Part 7 — Testing Checklist

Quick Audit (5 minutes)

  • Tab through entire page — can you reach and activate everything?
  • Focus indicator visible at all times?
  • Check color contrast with browser DevTools
  • Run axe DevTools or Lighthouse accessibility audit
  • Check page without CSS (semantic structure visible?)

Standard Audit (WCAG 2.2 AA)

Perceivable

  • All images have meaningful alt text (or alt="" for decorative)
  • Color contrast ≥4.5:1 normal text, ≥3:1 large text and UI elements
  • Color not the only way to convey information
  • Content readable/functional at 200% zoom
  • No horizontal scroll at 320px wide (reflow)
  • Text spacing overrides don't break layout
  • No auto-playing audio without controls

Operable

  • All functionality keyboard-accessible
  • No keyboard traps
  • Focus indicator always visible
  • Focus not obscured by sticky headers/footers
  • Skip navigation link present
  • All pages have descriptive <title>
  • Focus order is logical
  • Links have clear, descriptive text
  • Touch targets ≥24×24px with adequate spacing
  • No content flashes >3×/second

Understandable

  • <html lang="en"> set correctly
  • Language changes marked with lang attribute
  • No unexpected context changes on focus
  • Navigation consistent across pages
  • All form inputs have visible labels
  • Errors identified, described, and suggestions offered
  • Help mechanism in consistent location

Robust

  • All form controls have name, role, value via HTML or ARIA
  • Status messages announced without focus change
  • Valid, well-structured HTML (no broken nesting)
  • ARIA roles/states used correctly
  • Dynamic content updates announced

Screen Reader Testing

Test with: NVDA + Chrome (Windows), VoiceOver + Safari (Mac), TalkBack + Chrome (Android)

  • Heading structure navigable and logical
  • Landmarks navigable (nav, main, aside, footer)
  • Form labels read correctly
  • Error messages announced when fields are invalid
  • Dynamic content (alerts, status) announced
  • Modals: focus traps, Escape closes, focus returns

Part 8 — Legal and Standards Context

StandardJurisdictionBased On
ADA (Americans with Disabilities Act)USAWCAG 2.1 AA
Section 508USA (Federal)WCAG 2.0 AA
EN 301 549European UnionWCAG 2.1 AA
AODAOntario, CanadaWCAG 2.0 AA
Equality Act 2010UKWCAG 2.1 AA
BS 8878UKWCAG 2.1
BITV 2.0GermanyWCAG 2.1 AA

Recommended target: WCAG 2.2 AA — exceeds requirements of all major laws and is future-proof.


Part 9 — Tools and Resources

Testing Tools

ToolTypeUse For
axe DevToolsBrowser extension + CIMost comprehensive automated checks
WAVE (WebAIM)Browser extensionVisual error overlay
LighthouseChrome DevToolsQuick baseline audit
Colour Contrast AnalyserDesktop appPixel-accurate contrast check
WebAIM Contrast CheckerWeb toolQuick contrast calculation
NVDAScreen readerWindows screen reader testing (free)
VoiceOverScreen readerMac/iOS built-in
TalkBackScreen readerAndroid built-in
KeyboardManualAlways test keyboard-only navigation

Key References


Summary: Non-Negotiable AA Rules

When building any frontend interface, these WCAG 2.2 AA rules are always required:

  1. Contrast: 4.5:1 normal text, 3:1 large text and UI components
  2. Alt text: Every informative image has descriptive alt; decorative has alt=""
  3. Keyboard: 100% of functionality reachable and operable via keyboard alone
  4. Focus visible: Visible focus indicator that is never removed
  5. Labels: Every form input has an associated visible <label>
  6. Errors: Errors identified in text, not color alone; aria-invalid used; suggestions provided
  7. Headings: Logical hierarchy, no skipped levels
  8. Language: <html lang="..."> always set
  9. Semantic HTML: Use <button>, <a>, <nav>, <main> etc., not <div> substitutes
  10. No color-only info: Never use color as the sole means to convey any information
  11. Reflow: Content usable at 320px width without horizontal scrolling
  12. Touch targets: Interactive elements ≥24×24px or adequate spacing
  13. Focus not obscured: Sticky headers/footers don't hide focused elements
  14. Status messages: Dynamic status/error messages announced to screen readers
  15. Consistent help: Help mechanisms in the same location across pages

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

framer-motion

No summary provided by upstream source.

Repository SourceNeeds Review
Security

audit-website

Audit websites for SEO, performance, security, technical, content, and 15 other issue cateories with 230+ rules using the squirrelscan CLI. Returns LLM-optimized reports with health scores, broken links, meta tag analysis, and actionable recommendations. Use to discover and asses website or webapp issues and health.

Repository Source
Security

better-auth-security-best-practices

No summary provided by upstream source.

Repository SourceNeeds Review
Security

swiftui-performance-audit

No summary provided by upstream source.

Repository SourceNeeds Review