Modern CSS Accessibility Features

1. prefers-reduced-motion Implementation

Motion Type Default Behavior Reduced Motion Alternative WCAG Reference
Page transitions Slide/fade animations (200-400ms) Instant or crossfade (50ms) 2.3.3 AAA (Animation from Interactions)
Scroll-triggered animations Elements slide/fade in on scroll Elements appear immediately (opacity: 1) 2.2.2 AA (Pause, Stop, Hide)
Hover effects Transform scale/rotate Color/opacity change only User preference respect
Loading spinners Rotating animation Pulsing opacity or static icon with ARIA Reduce vestibular triggers
Parallax effects Multi-layer scrolling Disable completely (single-layer scroll) 2.3.3 AAA
Auto-playing video Background video loops Static image or paused video 2.2.2 AA (5-second rule)
Carousel auto-advance Slides change every 5s Disable auto-advance completely 2.2.2 AA

Example: Comprehensive prefers-reduced-motion implementation

/* Default: animations enabled */
.slide-in {
  animation: slideIn 0.3s ease-out;
}

@keyframes slideIn {
  from { 
    transform: translateY(20px); 
    opacity: 0; 
  }
  to { 
    transform: translateY(0); 
    opacity: 1; 
  }
}

/* Reduced motion: instant appearance */
@media (prefers-reduced-motion: reduce) {
  .slide-in {
    animation: none;
    /* Maintain end state without animation */
    transform: translateY(0);
    opacity: 1;
  }
  
  /* Reduce all animations and transitions */
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Safe alternative: crossfade instead of slide */
@media (prefers-reduced-motion: reduce) {
  .modal {
    animation: fadeIn 0.15s ease-in;
  }
  
  @keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
  }
}
Critical: Never use animation: none without preserving the end state. Users with vestibular disorders can experience nausea, dizziness, and migraines from parallax, zoom, and rotation effects. Always test with OS setting enabled.

2. prefers-color-scheme Support

Feature Light Mode Dark Mode Contrast Requirement
Background color #ffffff or light neutrals #000000, #121212, #1e1e1e Maintain 4.5:1 text contrast
Text color #000000, #333333, #1a1a1a #ffffff, #e0e0e0, #f5f5f5 4.5:1 for body, 3:1 for large text
Link color #0066cc, #1a73e8 #66b3ff, #8ab4f8 4.5:1 on background + underline
Border/divider #e0e0e0, #d0d0d0 #404040, #505050 3:1 against adjacent colors
Focus indicator #0066cc (blue) #66b3ff (lighter blue) 3:1 minimum (WCAG 2.4.13)
Code blocks Light syntax highlighting Dark syntax highlighting Each token needs 4.5:1 contrast
Images/logos Default version Inverted or alternate version Use picture element or CSS filter

Example: Dark mode with CSS custom properties

:root {
  /* Light mode (default) */
  --bg-primary: #ffffff;
  --bg-secondary: #f5f5f5;
  --text-primary: #1a1a1a;
  --text-secondary: #666666;
  --accent: #0066cc;
  --border: #e0e0e0;
  --focus-ring: #0066cc;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg-primary: #1e1e1e;
    --bg-secondary: #2d2d2d;
    --text-primary: #e0e0e0;
    --text-secondary: #a0a0a0;
    --accent: #66b3ff;
    --border: #404040;
    --focus-ring: #66b3ff;
  }
  
  /* Adjust images for dark mode */
  img:not([src*=".svg"]) {
    filter: brightness(0.9);
  }
  
  /* Invert logos that need it */
  .logo {
    filter: invert(1);
  }
}

body {
  background: var(--bg-primary);
  color: var(--text-primary);
}

a {
  color: var(--accent);
  text-decoration: underline;
}

:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
Dark Mode Best Practices: Use CSS custom properties for easy theme switching. Test contrast ratios in both modes. Provide manual toggle that persists user preference. Avoid pure black (#000) backgrounds (use #121212 or #1e1e1e for better readability).

3. prefers-contrast Handling

Preference Value User Need Design Adjustment Browser Support
no-preference Standard contrast (default) Use standard design system colors All modern browsers
more High contrast mode (Windows HC, increased contrast setting) Increase contrast to 7:1, thicker borders, stronger colors Chrome 96+, Edge 96+, Safari 14.1+
less Low contrast preference (light sensitivity) Reduce contrast slightly, softer colors Safari 14.1+, limited elsewhere
custom Custom contrast settings Respect system colors Future spec

Example: Responsive contrast adjustments

/* Standard contrast (default) */
.button {
  background: #0066cc;
  color: #ffffff;
  border: 1px solid #0052a3;
}

/* High contrast mode */
@media (prefers-contrast: more) {
  .button {
    background: #003d7a; /* Darker for more contrast */
    color: #ffffff;
    border: 2px solid #000000; /* Thicker, stronger border */
    font-weight: 600;
  }
  
  /* Ensure all UI components meet 7:1 ratio */
  body {
    --contrast-ratio: 7;
  }
  
  /* Stronger focus indicators */
  :focus-visible {
    outline: 3px solid #000000;
    outline-offset: 3px;
  }
}

/* Low contrast mode (light sensitivity) */
@media (prefers-contrast: less) {
  .button {
    background: #3385d6; /* Lighter blue */
    color: #f5f5f5;
    border: 1px solid #5c9dd9;
  }
  
  /* Reduce harsh contrasts */
  body {
    background: #f8f8f8;
    color: #3a3a3a;
  }
}

/* Windows High Contrast Mode detection */
@media (prefers-contrast: more) and (prefers-color-scheme: dark) {
  /* User is in Windows High Contrast Dark theme */
  .card {
    border: 2px solid ButtonText;
    background: Canvas;
    color: CanvasText;
  }
}
Windows High Contrast Mode: When enabled, Windows forces system colors. Use prefers-contrast: more to detect and adjust. Test with forced-colors media query. Never use background images for critical info in high contrast mode.

4. CSS Container Queries for Accessibility

Use Case Accessibility Benefit Implementation Browser Support
Responsive text sizing Text adapts to container width, not viewport Scale fonts based on component size for better readability Chrome 105+, Safari 16+, Firefox 110+
Component-level zoom Better reflow at high zoom levels Layouts adjust independently at 200%+ zoom Meets WCAG 1.4.10 (Reflow)
Touch target sizing Increase button size in narrow containers Ensure 44px minimum in constrained spaces Dynamic WCAG 2.5.5 compliance
Reading line length Maintain optimal 50-75 character line length Adjust columns based on container width Improves readability (WCAG 1.4.8 AAA)
Focus indicator scaling Focus rings scale with component size Larger focus indicators in larger containers Better WCAG 2.4.13 compliance

Example: Accessible container queries

/* Enable container queries */
.card-container {
  container-type: inline-size;
  container-name: card;
}

/* Default: narrow card */
.card {
  padding: 16px;
  font-size: 14px;
}

.card h2 {
  font-size: 18px;
}

.card button {
  min-height: 44px;
  padding: 8px 16px;
}

/* Medium container: increase spacing and text */
@container card (min-width: 400px) {
  .card {
    padding: 24px;
    font-size: 16px;
  }
  
  .card h2 {
    font-size: 24px;
    line-height: 1.3;
  }
  
  .card button {
    min-height: 48px;
    padding: 12px 24px;
    font-size: 16px;
  }
}

/* Large container: optimal reading layout */
@container card (min-width: 600px) {
  .card {
    padding: 32px;
    max-width: 65ch; /* Optimal line length */
  }
  
  .card p {
    font-size: 18px;
    line-height: 1.6;
    margin-bottom: 1.5em;
  }
}

/* Container query for zoom support */
@container (max-width: 320px) {
  /* When container is narrow (e.g., at 200% zoom) */
  .card {
    /* Stack elements vertically */
    flex-direction: column;
  }
  
  .card button {
    width: 100%;
  }
}
Container Query Benefits: Better reflow support at high zoom levels (WCAG 1.4.10). Components adapt to available space, not just viewport. Improves readability by maintaining optimal line lengths. Enables truly responsive components in complex layouts.

5. CSS Focus-visible Selectors

Selector When Applied Use Case Browser Support
:focus Element has focus (keyboard or mouse) Basic focus state - always shown All browsers
:focus-visible Keyboard focus only (heuristic-based) Show focus ring for keyboard, hide for mouse clicks All modern browsers
:focus-within Element or descendant has focus Style parent when child is focused All modern browsers
:focus-visible:not(:focus) Never (invalid) Error: these states are mutually dependent N/A
:has(:focus-visible) Descendant has keyboard focus Style container when child has focus (alternative to :focus-within) Chrome 105+, Safari 15.4+

Example: Comprehensive focus-visible implementation

/* Remove default focus outline (be careful!) */
*:focus {
  outline: none;
}

/* Custom focus-visible for keyboard navigation */
*:focus-visible {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
  border-radius: 2px;
}

/* Button: show focus only for keyboard */
.button {
  border: 2px solid transparent;
  transition: border-color 0.15s;
}

.button:focus-visible {
  border-color: #0066cc;
  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.2);
}

/* Input: always show focus (user is typing) */
input,
textarea,
select {
  border: 1px solid #d0d0d0;
}

input:focus,
textarea:focus,
select:focus {
  outline: 2px solid #0066cc;
  outline-offset: 0;
  border-color: #0066cc;
}

/* Focus-within: highlight form section when any field focused */
.form-section:focus-within {
  background: #f5f9ff;
  border-color: #0066cc;
}

/* High contrast mode: ensure focus is visible */
@media (prefers-contrast: more) {
  *:focus-visible {
    outline: 3px solid currentColor;
    outline-offset: 3px;
  }
}

/* Dark mode focus adjustment */
@media (prefers-color-scheme: dark) {
  *:focus-visible {
    outline-color: #66b3ff;
  }
}
Focus-visible Warning: Never use outline: none without providing an alternative focus indicator. Always test with keyboard navigation. WCAG 2.4.13 requires 3:1 contrast ratio for focus indicators against adjacent colors. Some browsers may show :focus-visible for mouse clicks on form inputs.

6. Scroll Behavior and Animations

CSS Property Accessibility Impact Best Practice WCAG Reference
scroll-behavior: smooth Can cause motion sickness if animated Disable in prefers-reduced-motion 2.3.3 AAA (Animation from Interactions)
scroll-margin-top Prevents content from hiding under sticky headers Set to header height for skip links and anchor navigation 2.4.1 AA (Bypass Blocks)
scroll-padding Ensures focused elements fully visible Add padding for sticky UI when element scrolled into view 2.4.7 AA (Focus Visible)
overscroll-behavior Prevents unwanted scroll chaining Use 'contain' for modals to prevent body scroll User experience improvement
scroll-snap-type Can trap keyboard users if not careful Ensure keyboard can reach all snap points, provide skip option 2.1.1 AA (Keyboard)
position: sticky Can obscure content if not accounted for Use with scroll-margin-top on target elements 1.4.10 AA (Reflow)

Example: Accessible scroll behavior

/* Enable smooth scrolling (with reduced motion respect) */
html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

/* Sticky header with scroll compensation */
header {
  position: sticky;
  top: 0;
  height: 60px;
  background: white;
  z-index: 100;
}

/* Ensure anchored content visible below sticky header */
:target {
  scroll-margin-top: 80px; /* Header height + spacing */
}

/* Alternative: all headings (for skip links) */
h1, h2, h3, h4, h5, h6 {
  scroll-margin-top: 80px;
}

/* Focus visibility with sticky elements */
html {
  scroll-padding-top: 80px;
}

/* Accessible carousel with snap scrolling */
.carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
}

@media (prefers-reduced-motion: reduce) {
  .carousel {
    scroll-snap-type: none;
    scroll-behavior: auto;
  }
}

.carousel-item {
  scroll-snap-align: start;
  scroll-snap-stop: always;
  flex-shrink: 0;
  width: 100%;
}

/* Modal: prevent background scrolling */
.modal {
  overscroll-behavior: contain;
}

body.modal-open {
  overflow: hidden;
}

/* Smooth scroll for skip links */
.skip-link:focus {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  .skip-link:focus {
    scroll-behavior: auto;
  }
}
Scroll Accessibility Tips: Always respect prefers-reduced-motion for smooth scrolling. Use scroll-margin-top for all anchor targets and headings when using sticky headers. Test keyboard navigation with scroll-snap (ensure all content reachable). Prevent modal background scroll with overscroll-behavior: contain.

Modern CSS Accessibility Quick Reference

  • Reduced Motion: Use @media (prefers-reduced-motion: reduce) to disable/minimize animations; provide instant or crossfade alternatives to slide/parallax effects (WCAG 2.3.3)
  • Color Scheme: Implement dark mode with prefers-color-scheme; maintain 4.5:1 contrast in both modes; use CSS custom properties for theming
  • Contrast: Support prefers-contrast: more for high contrast mode; increase to 7:1 ratios, thicker borders, stronger focus indicators
  • Container Queries: Use for better reflow at zoom levels; maintain optimal line lengths (50-75ch); ensure touch targets scale appropriately
  • Focus-Visible: Show focus rings for keyboard only with :focus-visible; maintain 3:1 contrast for focus indicators (WCAG 2.4.13)
  • Scroll Behavior: Set scroll-behavior: auto for reduced motion; use scroll-margin-top for sticky headers; ensure scroll-snap doesn't trap keyboard users
  • Testing: Test with OS accessibility settings enabled; verify all media queries work; use browser DevTools to simulate preferences
  • Browser Support: Most features in modern browsers (2022+); provide fallbacks or progressive enhancement for older browsers
  • Key Properties: scroll-margin, scroll-padding, overscroll-behavior, container-type, focus-visible, color-scheme
  • Tools: Chrome DevTools (Rendering panel), Firefox Accessibility Inspector, Safari Develop menu (prefers simulation)