CSS Accessibility and User Preferences

1. Focus Management and Visible Indicators

Pseudo-class Description Use Case WCAG Level
:focus Element has focus (keyboard/click) Basic focus styling WCAG 2.4.7 (AA)
:focus-visible Focus from keyboard only Show outline for keyboard navigation Best practice
:focus-within Element or descendant has focus Highlight parent containers Enhancement
outline Focus indicator property 3px minimum recommended WCAG 2.4.11 (AA)
outline-offset Space between element and outline Improve visual clarity Enhancement
:focus-visible + :has() Advanced focus patterns Complex component focus Modern

Example: Accessible focus indicators

/* ❌ NEVER do this */
* {
    outline: none;  /* Breaks keyboard accessibility! */
}

/* ✅ GOOD: Basic focus styling */
a:focus,
button:focus,
input:focus,
textarea:focus,
select:focus {
    outline: 2px solid #007acc;
    outline-offset: 2px;
}

/* ✅ BETTER: Use :focus-visible for keyboard-only focus */
a:focus-visible,
button:focus-visible,
input:focus-visible {
    outline: 3px solid #007acc;
    outline-offset: 2px;
}

/* Remove focus ring for mouse users */
a:focus:not(:focus-visible),
button:focus:not(:focus-visible) {
    outline: none;
}

/* Enhanced focus with multiple indicators */
.button:focus-visible {
    outline: 3px solid #007acc;
    outline-offset: 2px;
    box-shadow: 0 0 0 4px rgba(0, 122, 204, 0.2);
}

/* Focus-within for containers */
.form-group:focus-within {
    border-color: #007acc;
    background: rgba(0, 122, 204, 0.05);
}

.card:focus-within {
    box-shadow: 0 0 0 3px #007acc;
}

/* Skip links (keyboard navigation) */
.skip-link {
    position: absolute;
    top: -40px;
    left: 0;
    background: #007acc;
    color: white;
    padding: 0.5rem 1rem;
    z-index: 1000;
}

.skip-link:focus {
    top: 0;
}

/* High contrast focus indicators */
@media (prefers-contrast: high) {
    *:focus-visible {
        outline: 4px solid currentColor;
        outline-offset: 2px;
    }
}

/* Focus trap for modals */
.modal:focus-within {
    /* Keep focus visible within modal */
}

.modal-backdrop {
    /* Prevent focus behind modal */
    pointer-events: none;
}

/* Visible focus for all interactive elements */
a, button, input, select, textarea,
[tabindex]:not([tabindex="-1"]) {
    &:focus-visible {
        outline: 3px solid #007acc;
        outline-offset: 2px;
    }
}

/* Custom focus indicators */
.custom-button:focus-visible {
    outline: none;  /* Remove default */
    box-shadow: 
        0 0 0 3px white,
        0 0 0 6px #007acc;
}

/* Focus indicator requirements:
   WCAG 2.4.7: Focus visible (Level AA)
   - Minimum 3:1 contrast ratio
   - At least 2px thickness or equivalent
   - Clearly visible against all backgrounds
*/

Example: Advanced focus patterns

/* Focus management for complex components */

/* Tab panel focus */
.tab-list {
    display: flex;
    border-bottom: 2px solid #e0e0e0;
}

.tab {
    padding: 0.75rem 1.5rem;
    border: none;
    background: transparent;
    cursor: pointer;
    position: relative;
}

.tab:focus-visible {
    outline: 3px solid #007acc;
    outline-offset: -3px;
    z-index: 1;
}

.tab[aria-selected="true"] {
    border-bottom: 3px solid #007acc;
    font-weight: bold;
}

/* Dropdown menu focus */
.dropdown-menu {
    list-style: none;
    padding: 0.5rem 0;
}

.dropdown-item:focus-visible {
    outline: none;
    background: #007acc;
    color: white;
}

/* Keyboard navigation indicator */
.list-item[data-keyboard-selected] {
    background: rgba(0, 122, 204, 0.1);
    outline: 2px solid #007acc;
    outline-offset: -2px;
}

/* Focus visible within forms */
.form-field:has(input:focus-visible) label {
    color: #007acc;
    font-weight: 500;
}

.form-field:has(input:focus-visible) {
    border-left: 3px solid #007acc;
    padding-left: 1rem;
}

/* Card focus with nested interactive elements */
.card:has(:focus-visible) {
    box-shadow: 0 0 0 3px #007acc;
}

/* Focus for custom controls */
.custom-checkbox {
    position: relative;
    display: inline-block;
    width: 20px;
    height: 20px;
    border: 2px solid #666;
    border-radius: 4px;
}

.custom-checkbox:has(input:focus-visible) {
    outline: 3px solid #007acc;
    outline-offset: 2px;
}

input[type="checkbox"] {
    position: absolute;
    opacity: 0;
    width: 100%;
    height: 100%;
    cursor: pointer;
}

/* Roving tabindex for lists */
.list[role="list"] {
    display: flex;
    flex-direction: column;
}

.list-item[tabindex="0"]:focus-visible {
    outline: 3px solid #007acc;
    outline-offset: -3px;
    background: rgba(0, 122, 204, 0.1);
}

.list-item[tabindex="-1"] {
    /* Not in tab order */
}

/* Focus indicator animation */
@keyframes focus-pulse {
    0%, 100% {
        box-shadow: 0 0 0 3px rgba(0, 122, 204, 0.4);
    }
    50% {
        box-shadow: 0 0 0 6px rgba(0, 122, 204, 0.1);
    }
}

.important-action:focus-visible {
    animation: focus-pulse 2s ease-in-out infinite;
}
Note: Never remove focus indicators without replacement. Use :focus-visible to show focus only for keyboard users. Minimum 3:1 contrast ratio for focus indicators (WCAG 2.4.11). Test with keyboard-only navigation.

2. Color Contrast and WCAG Compliance

WCAG Level Normal Text Large Text Graphics/UI
AA (Minimum) 4.5:1 contrast 3:1 contrast (18px+) 3:1 contrast
AAA (Enhanced) 7:1 contrast 4.5:1 contrast (18px+) 3:1 contrast
Large Text 18px+ or 14px+ bold
Non-text Contrast 3:1 for icons, borders, focus indicators

Example: WCAG compliant color schemes

/* WCAG AA compliant color combinations */

/* ✅ PASS AA: 4.52:1 contrast */
.text-on-light {
    color: #595959;  /* Dark gray */
    background: #ffffff;  /* White */
}

/* ✅ PASS AAA: 7.03:1 contrast */
.text-high-contrast {
    color: #3d3d3d;  /* Darker gray */
    background: #ffffff;  /* White */
}

/* ✅ PASS AA for large text: 3.01:1 */
.large-text {
    font-size: 18px;
    color: #767676;
    background: #ffffff;
}

/* ❌ FAIL AA: 2.85:1 contrast */
.insufficient-contrast {
    color: #999999;  /* Too light */
    background: #ffffff;
}

/* Color palette with contrast ratios */
:root {
    /* Primary colors with AA compliance */
    --primary: #007acc;  /* 4.54:1 on white */
    --primary-dark: #005a9e;  /* 6.59:1 on white - AAA */
    --primary-light: #3399dd;  /* 3.02:1 on white - AA large */
    
    /* Text colors */
    --text-primary: #1a1a1a;  /* 16.59:1 on white - AAA */
    --text-secondary: #4d4d4d;  /* 8.59:1 on white - AAA */
    --text-tertiary: #666666;  /* 5.74:1 on white - AA */
    
    /* Background colors */
    --bg-primary: #ffffff;
    --bg-secondary: #f5f5f5;
    --bg-tertiary: #e0e0e0;
    
    /* Status colors (AA compliant) */
    --success: #0f7b0f;  /* 4.51:1 on white */
    --warning: #856404;  /* 5.51:1 on white */
    --error: #c41e3a;  /* 5.14:1 on white */
    --info: #006699;  /* 5.52:1 on white */
}

/* High contrast theme */
.high-contrast {
    --text-primary: #000000;
    --bg-primary: #ffffff;
    --link-color: #0000ff;
    --link-visited: #800080;
}

/* Accessible link colors */
a {
    color: #0066cc;  /* 5.54:1 on white - AA */
    text-decoration: underline;  /* Don't rely on color alone */
}

a:visited {
    color: #663399;  /* 5.04:1 on white - AA */
}

a:hover,
a:focus {
    color: #004080;  /* 7.52:1 on white - AAA */
    text-decoration: underline;
}

/* Button contrast */
.button-primary {
    background: #0066cc;  /* 5.54:1 on white */
    color: #ffffff;  /* 5.54:1 on #0066cc */
    border: 2px solid #0066cc;
}

.button-primary:hover {
    background: #0052a3;  /* Higher contrast */
}

/* Disabled state must still meet 3:1 for UI components */
.button:disabled {
    background: #cccccc;
    color: #666666;  /* 3.13:1 - passes 3:1 for graphics */
    opacity: 1;  /* Don't use opacity alone for disabled state */
}

/* Status indicators with patterns (not color alone) */
.status-success {
    color: #0f7b0f;
    background: #e6f4e6;
}

.status-success::before {
    content: '✓ ';  /* Icon reinforces status */
}

.status-error {
    color: #c41e3a;
    background: #fce8eb;
    border-left: 4px solid currentColor;  /* Additional indicator */
}

.status-error::before {
    content: '✗ ';
}

/* Form validation */
.input-error {
    border-color: #c41e3a;  /* 5.14:1 */
    border-width: 2px;
}

.error-message {
    color: #c41e3a;
    font-weight: 500;
}

.error-message::before {
    content: '⚠ ';  /* Icon supplements color */
}

/* Testing contrast with CSS */
/*
To test, use browser DevTools or online tools:
- WebAIM Contrast Checker
- Chrome DevTools (Accessibility panel)
- Firefox Accessibility Inspector
- https://contrast-ratio.com
*/

Example: Dynamic contrast adjustment

/* Automatic contrast adjustment with color-contrast() (experimental) */
.adaptive-text {
    /* Choose text color with best contrast */
    color: color-contrast(var(--bg-color) vs white, black);
}

/* Relative color syntax for guaranteed contrast */
.button {
    --bg: #007acc;
    background: var(--bg);
    /* Ensure text contrast by darkening/lightening */
    color: oklch(from var(--bg) calc(l - 50%) c h);
}

/* Manual contrast function (CSS custom property) */
:root {
    --bg-lightness: 95%;  /* Light background */
}

.auto-contrast {
    background: hsl(0, 0%, var(--bg-lightness));
    /* If background is light (>50%), use dark text, else light text */
    color: hsl(0, 0%, calc((var(--bg-lightness) - 50%) * -100%));
}

/* Prefers-contrast media query */
@media (prefers-contrast: more) {
    :root {
        --text-primary: #000000;
        --bg-primary: #ffffff;
        --link-color: #0000ff;
    }
    
    .button {
        border-width: 2px;
        font-weight: 600;
    }
    
    a {
        text-decoration: underline;
        text-decoration-thickness: 2px;
    }
}

@media (prefers-contrast: less) {
    /* Some users prefer lower contrast */
    :root {
        --text-primary: #4d4d4d;
        --bg-primary: #fafafa;
    }
}

/* Force colors mode (Windows High Contrast) */
@media (forced-colors: active) {
    .button {
        border: 1px solid currentColor;
    }
    
    .card {
        border: 1px solid CanvasText;
    }
    
    /* System colors in forced-colors mode:
       Canvas, CanvasText, LinkText, VisitedText,
       ActiveText, ButtonFace, ButtonText, Field,
       FieldText, Highlight, HighlightText, GrayText
    */
}

/* Contrast checker utility class */
.contrast-check {
    /* For development: show contrast issues */
    outline: 3px solid red;
}

.contrast-check[data-contrast="pass-aaa"] {
    outline-color: green;
}

.contrast-check[data-contrast="pass-aa"] {
    outline-color: yellow;
}

.contrast-check[data-contrast="fail"] {
    outline-color: red;
}
Warning: Don't use color as the only means of conveying information. Add icons, patterns, or text. Test with color blindness simulators. Aim for WCAG AA minimum (4.5:1 normal text, 3:1 large text).

3. prefers-reduced-motion Implementation

Value Meaning Action Priority
no-preference No preference indicated Use normal animations Default
reduce User prefers reduced motion Disable/minimize animations WCAG 2.3.3 (AAA)

Example: Reduced motion patterns

/* Animations with reduced motion support */

/* Default: Full animations */
.card {
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.card:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 16px rgba(0,0,0,0.2);
}

/* Reduced motion: Instant or minimal animation */
@media (prefers-reduced-motion: reduce) {
    .card {
        transition: none;
    }
    
    .card:hover {
        /* Still provide feedback, but instantly */
        transform: none;
        box-shadow: 0 0 0 3px #007acc;
    }
}

/* ✅ BETTER: Use custom property approach */
:root {
    --animation-duration: 0.3s;
    --animation-easing: ease;
}

@media (prefers-reduced-motion: reduce) {
    :root {
        --animation-duration: 0.01ms;  /* Near-instant */
        --animation-easing: linear;
    }
}

.animated-element {
    transition: 
        transform var(--animation-duration) var(--animation-easing),
        opacity var(--animation-duration) var(--animation-easing);
}

/* Disable decorative animations, keep functional ones */
@media (prefers-reduced-motion: reduce) {
    /* Disable: Decorative animations */
    .decorative-spin {
        animation: none;
    }
    
    .parallax-bg {
        transform: none !important;
    }
    
    /* Keep: Functional animations (loading, progress) */
    .spinner {
        /* Keep rotation but reduce speed */
        animation-duration: 2s;
    }
    
    .progress-bar {
        /* Keep progress indication */
        transition: width 0.1s linear;
    }
}

/* Safe animation defaults */
* {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
}

/* Then opt-in animations where appropriate */
@media (prefers-reduced-motion: no-preference) {
    * {
        animation-duration: revert;
        animation-iteration-count: revert;
        transition-duration: revert;
        scroll-behavior: revert;
    }
}

/* Reduce but don't eliminate */
@media (prefers-reduced-motion: reduce) {
    .modal {
        /* Instead of slide-in, use fade */
        animation: fadeIn 0.15s ease;
    }
    
    @keyframes fadeIn {
        from { opacity: 0; }
        to { opacity: 1; }
    }
}

/* Scroll-triggered animations */
.reveal-on-scroll {
    opacity: 0;
    transform: translateY(20px);
    transition: opacity 0.6s, transform 0.6s;
}

.reveal-on-scroll.visible {
    opacity: 1;
    transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
    .reveal-on-scroll {
        opacity: 1;
        transform: none;
        transition: none;
    }
}

/* Skeleton loading */
.skeleton {
    background: linear-gradient(
        90deg,
        #f0f0f0 25%,
        #e0e0e0 50%,
        #f0f0f0 75%
    );
    background-size: 200% 100%;
    animation: skeleton-loading 1.5s ease-in-out infinite;
}

@keyframes skeleton-loading {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
    .skeleton {
        animation: none;
        background: #e0e0e0;  /* Static background */
    }
}

/* Auto-playing carousels */
.carousel {
    /* Auto-advance by default */
}

@media (prefers-reduced-motion: reduce) {
    .carousel {
        /* Disable auto-advance */
        animation-play-state: paused;
    }
}

/* Notification animations */
.toast {
    animation: slideIn 0.3s ease, slideOut 0.3s ease 3s;
}

@media (prefers-reduced-motion: reduce) {
    .toast {
        /* Instant appearance, longer display time */
        animation: fadeIn 0.1s ease, fadeOut 0.1s ease 5s;
    }
}

Example: Comprehensive motion reduction

/* Global reduced motion reset */
@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;
    }
    
    /* Exceptions for critical animations */
    .loading-spinner,
    .progress-indicator,
    [role="progressbar"] {
        animation-duration: revert !important;
        transition-duration: revert !important;
    }
}

/* Motion safe wrapper */
@media (prefers-reduced-motion: no-preference) {
    .motion-safe-fade {
        animation: fadeIn 0.5s ease;
    }
    
    .motion-safe-slide {
        animation: slideUp 0.3s ease;
    }
    
    .motion-safe-scale {
        transition: transform 0.3s ease;
    }
    
    .motion-safe-scale:hover {
        transform: scale(1.05);
    }
}

/* Provide alternative feedback for reduced motion */
@media (prefers-reduced-motion: reduce) {
    .interactive-card:hover {
        /* Instead of animation, use border/outline */
        outline: 3px solid #007acc;
        outline-offset: 2px;
    }
    
    .notification {
        /* Instead of slide-in, just appear with border */
        border-left: 4px solid #007acc;
    }
    
    .loading-indicator {
        /* Instead of spinner, use pulsing dot */
        animation: pulse 2s ease-in-out infinite;
    }
    
    @keyframes pulse {
        0%, 100% { opacity: 1; }
        50% { opacity: 0.5; }
    }
}

/* JavaScript detection */
/*
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');

if (prefersReducedMotion.matches) {
    // Disable complex animations
    document.body.classList.add('reduce-motion');
}

// Listen for changes
prefersReducedMotion.addEventListener('change', (e) => {
    if (e.matches) {
        document.body.classList.add('reduce-motion');
    } else {
        document.body.classList.remove('reduce-motion');
    }
});
*/

/* CSS for JS-controlled animations */
.reduce-motion .animated-element {
    animation: none !important;
    transition: none !important;
}

/* Best practices:
   1. Respect user preference always
   2. Keep functional animations (loading, progress)
   3. Provide alternative feedback (borders, colors)
   4. Test with reduced motion enabled
   5. Consider duration reduction vs complete removal
   6. Make smooth scrolling optional
   7. Pause auto-playing content
*/
Note: Always respect prefers-reduced-motion. Keep functional animations (loading spinners, progress bars). Provide alternative feedback with borders, colors, or static states. Test with system settings enabled.

4. prefers-color-scheme and System Themes

Value Description Implementation Fallback
light User prefers light theme Light colors, dark text Default
dark User prefers dark theme Dark colors, light text All modern browsers
no-preference No preference indicated Use default theme Fallback

Example: System theme implementation

/* Modern approach: CSS custom properties */
:root {
    color-scheme: light dark;  /* Opt-in to browser dark mode */
    
    /* Light theme (default) */
    --bg-primary: #ffffff;
    --bg-secondary: #f5f5f5;
    --text-primary: #1a1a1a;
    --text-secondary: #666666;
    --border-color: #e0e0e0;
    --link-color: #0066cc;
    --shadow: rgba(0, 0, 0, 0.1);
}

@media (prefers-color-scheme: dark) {
    :root {
        /* Dark theme */
        --bg-primary: #1a1a1a;
        --bg-secondary: #2d2d2d;
        --text-primary: #f0f0f0;
        --text-secondary: #b3b3b3;
        --border-color: #404040;
        --link-color: #6db3f2;
        --shadow: rgba(0, 0, 0, 0.5);
    }
}

/* Apply variables */
body {
    background: var(--bg-primary);
    color: var(--text-primary);
}

.card {
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    box-shadow: 0 2px 4px var(--shadow);
}

a {
    color: var(--link-color);
}

/* Light-dark() function (modern browsers) */
.element {
    background: light-dark(white, #1a1a1a);
    color: light-dark(#1a1a1a, white);
}

/* Complete theme example */
:root {
    /* Neutral colors */
    --neutral-50: #fafafa;
    --neutral-100: #f5f5f5;
    --neutral-900: #1a1a1a;
    
    /* Brand colors (same in both themes) */
    --primary: #007acc;
    --success: #10b981;
    --warning: #f59e0b;
    --error: #ef4444;
}

/* Light theme */
:root {
    --bg-page: var(--neutral-50);
    --bg-surface: white;
    --bg-elevated: white;
    --text-primary: var(--neutral-900);
    --text-secondary: #666;
    --text-tertiary: #999;
    --border: #e0e0e0;
}

/* Dark theme */
@media (prefers-color-scheme: dark) {
    :root {
        --bg-page: #0a0a0a;
        --bg-surface: #1a1a1a;
        --bg-elevated: #2d2d2d;
        --text-primary: #f0f0f0;
        --text-secondary: #b3b3b3;
        --text-tertiary: #808080;
        --border: #404040;
    }
    
    /* Adjust images for dark mode */
    img {
        opacity: 0.9;
    }
    
    img:hover {
        opacity: 1;
    }
}

/* Component-specific dark mode adjustments */
@media (prefers-color-scheme: dark) {
    .codeBlock {
        background: #0d1117;
        border-color: #30363d;
    }
    
    .syntax-highlight {
        /* Adjust syntax highlighting colors */
        --keyword: #ff7b72;
        --string: #a5d6ff;
        --comment: #8b949e;
    }
    
    /* Invert icons for better visibility */
    .icon-dark-invert {
        filter: invert(1);
    }
}

Example: Advanced theming with user override

/* Three-way theme system: auto, light, dark */

/* Default: Auto (respects system preference) */
:root {
    color-scheme: light;
    --theme: light;
}

@media (prefers-color-scheme: dark) {
    :root {
        color-scheme: dark;
        --theme: dark;
    }
}

/* User override: Light theme */
[data-theme="light"] {
    color-scheme: light;
    --theme: light;
}

/* User override: Dark theme */
[data-theme="dark"] {
    color-scheme: dark;
    --theme: dark;
}

/* Theme-specific styles */
:root,
[data-theme="light"] {
    --bg-primary: #ffffff;
    --text-primary: #1a1a1a;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg-primary: #1a1a1a;
        --text-primary: #f0f0f0;
    }
}

[data-theme="dark"] {
    --bg-primary: #1a1a1a;
    --text-primary: #f0f0f0;
}

/* JavaScript for theme toggle */
/*
const themeToggle = document.querySelector('[data-theme-toggle]');
const currentTheme = localStorage.getItem('theme') || 'auto';

function setTheme(theme) {
    if (theme === 'auto') {
        document.documentElement.removeAttribute('data-theme');
    } else {
        document.documentElement.setAttribute('data-theme', theme);
    }
    localStorage.setItem('theme', theme);
}

themeToggle.addEventListener('click', () => {
    const current = document.documentElement.getAttribute('data-theme') || 'auto';
    const next = current === 'light' ? 'dark' : current === 'dark' ? 'auto' : 'light';
    setTheme(next);
});

// Apply saved theme
setTheme(currentTheme);

// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
    if (!document.documentElement.hasAttribute('data-theme')) {
        // Only react if in auto mode
        console.log('System theme changed:', e.matches ? 'dark' : 'light');
    }
});
*/

/* Smooth theme transitions */
body {
    transition: background-color 0.3s ease, color 0.3s ease;
}

@media (prefers-reduced-motion: reduce) {
    body {
        transition: none;
    }
}

/* Theme-aware components */
.theme-indicator {
    display: none;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .theme-indicator--dark {
        display: inline;
    }
}

[data-theme="dark"] .theme-indicator--dark {
    display: inline;
}

[data-theme="light"] .theme-indicator--light,
:root:not([data-theme="dark"]) .theme-indicator--light {
    display: inline;
}

/* Print styles (always light) */
@media print {
    :root {
        color-scheme: light;
        --bg-primary: white;
        --text-primary: black;
    }
}
Warning: Set color-scheme: light dark to enable browser dark mode for form controls. Test both themes for contrast compliance. Provide user override option. Don't forget to adjust images and icons for dark mode.

5. prefers-contrast and High Contrast Mode

Media Query Value Description Action
prefers-contrast no-preference No contrast preference Use default styles
prefers-contrast more User wants higher contrast Increase contrast, borders
prefers-contrast less User wants lower contrast Reduce contrast, soften
forced-colors active High contrast mode enabled Use system colors

Example: High contrast mode support

/* Increased contrast preference */
@media (prefers-contrast: more) {
    :root {
        /* Higher contrast colors */
        --text-primary: #000000;
        --bg-primary: #ffffff;
        --link-color: #0000ff;
        --border-color: #000000;
    }
    
    /* Thicker borders */
    .card,
    .button,
    input,
    textarea {
        border-width: 2px;
    }
    
    /* Stronger shadows */
    .elevated {
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
    }
    
    /* Bolder text */
    body {
        font-weight: 500;
    }
    
    h1, h2, h3, h4, h5, h6 {
        font-weight: 700;
    }
    
    /* Remove transparency */
    .translucent {
        opacity: 1;
    }
    
    /* Underline all links */
    a {
        text-decoration: underline;
        text-decoration-thickness: 2px;
    }
}

/* Reduced contrast preference */
@media (prefers-contrast: less) {
    :root {
        --text-primary: #4d4d4d;
        --bg-primary: #fafafa;
        --border-color: #e0e0e0;
    }
    
    /* Softer borders */
    .card {
        border-color: #f0f0f0;
    }
    
    /* Lighter shadows */
    .elevated {
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
    }
}

/* Windows High Contrast Mode (forced-colors) */
@media (forced-colors: active) {
    /* System will override most colors */
    /* Use system color keywords */
    
    body {
        background: Canvas;
        color: CanvasText;
    }
    
    a {
        color: LinkText;
    }
    
    a:visited {
        color: VisitedText;
    }
    
    .button {
        background: ButtonFace;
        color: ButtonText;
        border: 1px solid ButtonText;
    }
    
    .button:hover,
    .button:focus {
        background: Highlight;
        color: HighlightText;
        border-color: HighlightText;
        forced-color-adjust: none;  /* Opt out of adjustment */
    }
    
    /* Add borders where they don't exist */
    .card,
    .section {
        border: 1px solid CanvasText;
    }
    
    /* Show focus indicators */
    :focus-visible {
        outline: 3px solid Highlight;
        outline-offset: 2px;
    }
    
    /* Icons and decorative images */
    .icon {
        /* Force icons to be visible */
        forced-color-adjust: auto;
    }
    
    img {
        /* Preserve image colors */
        forced-color-adjust: none;
    }
    
    /* Backplate for text over images */
    .text-over-image {
        background: Canvas;
        color: CanvasText;
        padding: 0.5rem;
    }
    
    /* System color keywords:
       Canvas - Background
       CanvasText - Text on Canvas
       LinkText - Links
       VisitedText - Visited links
       ActiveText - Active link
       ButtonFace - Button background
       ButtonText - Button text
       ButtonBorder - Button border
       Field - Input background
       FieldText - Input text
       Highlight - Selected background
       HighlightText - Selected text
       GrayText - Disabled text
       Mark - Highlighted background
       MarkText - Highlighted text
    */
}

/* Combine prefers-contrast and forced-colors */
@media (prefers-contrast: more) {
    /* Additional enhancements even without forced-colors */
    .button {
        border-width: 2px;
        font-weight: 600;
    }
}

@media (prefers-contrast: more) and (forced-colors: active) {
    /* Extra emphasis in high contrast mode */
    .important {
        border: 3px solid HighlightText;
        font-weight: bold;
    }
}

Example: Comprehensive high contrast implementation

/* Complete high contrast strategy */

/* Normal mode */
.card {
    background: white;
    border: 1px solid #e0e0e0;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

/* Increased contrast */
@media (prefers-contrast: more) {
    .card {
        border: 2px solid #000;
        box-shadow: none;  /* Shadows may be confusing */
    }
}

/* Forced colors (Windows High Contrast) */
@media (forced-colors: active) {
    .card {
        background: Canvas;
        border: 1px solid CanvasText;
        box-shadow: none;
        
        /* Ensure content remains visible */
        color: CanvasText;
    }
    
    /* Custom focus indicators may be overridden */
    .card:focus-visible {
        outline: 3px solid Highlight;
        forced-color-adjust: none;
    }
    
    /* Preserve decorative borders */
    .status-success {
        border-left: 4px solid currentColor;
        forced-color-adjust: preserve-parent-color;
    }
    
    /* Buttons need explicit borders */
    .button {
        border: 2px solid ButtonText;
    }
    
    .button-primary {
        background: ButtonFace;
        color: ButtonText;
        border-color: ButtonText;
    }
    
    /* Disabled state */
    .button:disabled {
        color: GrayText;
        border-color: GrayText;
    }
    
    /* Form controls */
    input,
    textarea,
    select {
        background: Field;
        color: FieldText;
        border: 1px solid FieldText;
    }
    
    input:focus,
    textarea:focus,
    select:focus {
        outline: 2px solid HighlightText;
        outline-offset: 2px;
    }
    
    /* Icons need borders or backgrounds */
    .icon-only-button {
        border: 2px solid ButtonText;
        background: ButtonFace;
    }
    
    /* Ensure SVG icons are visible */
    svg {
        fill: currentColor;
        stroke: currentColor;
        forced-color-adjust: auto;
    }
    
    /* Graphs and charts */
    .chart {
        /* Provide text alternatives */
        forced-color-adjust: none;
        border: 2px solid CanvasText;
    }
    
    .chart::after {
        content: attr(aria-label);
        display: block;
        padding: 1rem;
        background: Canvas;
        color: CanvasText;
    }
}

/* Test for forced-colors support */
@supports (forced-color-adjust: none) {
    /* Browser supports forced-colors */
}

/* JavaScript detection */
/*
const forcedColors = window.matchMedia('(forced-colors: active)');
const prefersMoreContrast = window.matchMedia('(prefers-contrast: more)');

if (forcedColors.matches) {
    document.body.classList.add('forced-colors');
}

if (prefersMoreContrast.matches) {
    document.body.classList.add('high-contrast');
}
*/
Note: forced-colors: active indicates Windows High Contrast Mode. Use system colors (Canvas, CanvasText, LinkText, etc.). Add borders to borderless elements. Test with Windows High Contrast Mode enabled.

6. Accessible Typography and Reading Flow

Aspect Guideline WCAG Recommendation
Font Size Minimum 16px body text Best practice Use rem for scalability
Line Height 1.5 for body text minimum WCAG 1.4.8 (AAA) 1.5-1.8 for readability
Line Length 45-75 characters optimal WCAG 1.4.8 (AAA) Max 80ch recommended
Paragraph Spacing 1.5x font size minimum WCAG 1.4.12 (AA) Use margin-bottom
Letter Spacing 0.12em minimum WCAG 1.4.12 (AA) Adjustable by user
Word Spacing 0.16em minimum WCAG 1.4.12 (AA) Avoid justified text

Example: Accessible typography

/* WCAG compliant typography */
:root {
    /* Base font size */
    font-size: 16px;  /* Never below 16px */
}

body {
    font-family: system-ui, -apple-system, sans-serif;
    font-size: 1rem;  /* 16px, user scalable */
    line-height: 1.6;  /* WCAG 1.4.8: minimum 1.5 */
    color: #1a1a1a;
    
    /* Text spacing requirements (WCAG 1.4.12) */
    letter-spacing: 0.05em;
    word-spacing: 0.1em;
}

/* Headings */
h1 {
    font-size: 2.5rem;
    line-height: 1.2;
    margin-bottom: 1rem;
}

h2 {
    font-size: 2rem;
    line-height: 1.3;
    margin-bottom: 0.875rem;
}

h3 {
    font-size: 1.5rem;
    line-height: 1.4;
    margin-bottom: 0.75rem;
}

/* Paragraph spacing: 1.5x font size minimum */
p {
    margin-bottom: 1.5em;  /* WCAG 1.4.8 */
    max-width: 70ch;  /* Optimal line length */
}

/* Line length control */
.readable-content {
    max-width: 70ch;  /* 45-75 characters optimal */
    margin-left: auto;
    margin-right: auto;
}

/* Prevent long lines */
.article {
    max-width: min(70ch, 100% - 2rem);
}

/* Adjustable text spacing */
.user-adjustable-spacing {
    /* Allow user to override */
    letter-spacing: var(--user-letter-spacing, 0.05em);
    word-spacing: var(--user-word-spacing, 0.1em);
    line-height: var(--user-line-height, 1.6);
}

/* Avoid justified text (creates rivers) */
p {
    text-align: left;  /* Not justify */
}

/* Small text (captions, footnotes) */
.small-text {
    font-size: 0.875rem;  /* 14px minimum */
    line-height: 1.6;
}

/* Large text for better readability */
.large-text {
    font-size: 1.125rem;  /* 18px */
    line-height: 1.7;
}

/* Avoid narrow columns */
.column {
    min-width: 20ch;  /* Prevent narrow text */
}

/* Lists */
ul, ol {
    margin-bottom: 1.5em;
    padding-left: 2em;
}

li {
    margin-bottom: 0.5em;
    line-height: 1.6;
}

/* Code blocks */
code, pre {
    font-family: 'Monaco', 'Courier New', monospace;
    font-size: 0.875em;
    line-height: 1.6;
}

pre {
    padding: 1rem;
    overflow-x: auto;
    border-radius: 4px;
}

/* Blockquotes */
blockquote {
    margin: 1.5em 0;
    padding-left: 1.5em;
    border-left: 4px solid #007acc;
    font-style: italic;
    line-height: 1.7;
}

/* Links in text */
a {
    color: #0066cc;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
}

a:hover,
a:focus {
    text-decoration-thickness: 2px;
}

/* User preference: larger text spacing */
@media (prefers-contrast: more) {
    body {
        letter-spacing: 0.1em;
        word-spacing: 0.16em;
        line-height: 1.8;
    }
    
    p {
        margin-bottom: 2em;
    }
}

Example: Responsive and accessible typography

/* Fluid typography with clamp() */
:root {
    --font-size-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
    --font-size-lg: clamp(1.125rem, 1rem + 0.625vw, 1.25rem);
    --font-size-xl: clamp(1.25rem, 1.1rem + 0.75vw, 1.5rem);
    --font-size-2xl: clamp(1.5rem, 1.3rem + 1vw, 2rem);
    --font-size-3xl: clamp(2rem, 1.6rem + 2vw, 3rem);
}

body {
    font-size: var(--font-size-base);
    line-height: 1.6;
}

h1 {
    font-size: var(--font-size-3xl);
    line-height: 1.2;
}

/* Maintain readability at all sizes */
.content {
    /* Prevent text from getting too wide */
    max-width: min(70ch, 90vw);
    margin-inline: auto;
    padding-inline: 1rem;
}

/* Dyslexia-friendly options */
.dyslexia-friendly {
    font-family: 'OpenDyslexic', 'Comic Sans MS', sans-serif;
    font-size: 1.125rem;
    line-height: 1.8;
    letter-spacing: 0.1em;
    word-spacing: 0.2em;
}

/* Text that wraps nicely */
h1, h2, h3 {
    text-wrap: balance;  /* Balance lines */
    max-width: 30ch;
}

p {
    text-wrap: pretty;  /* Prevent orphans */
}

/* Avoid ALL CAPS */
.avoid-caps {
    /* Don't use text-transform: uppercase for long text */
    /* If necessary, use title case instead */
}

/* Underlining without cutting descenders */
a {
    text-decoration-skip-ink: auto;
}

/* Focus on readability, not aesthetics */
.readable-first {
    /* Prioritize these properties */
    font-size: 1.125rem;
    line-height: 1.7;
    letter-spacing: 0.02em;
    max-width: 65ch;
}

/* Tables */
table {
    font-size: 0.9375rem;
    line-height: 1.6;
}

th, td {
    padding: 0.75rem 1rem;
    text-align: left;
}

/* Prevent orphans and widows */
p {
    orphans: 2;
    widows: 2;
}

/* User zoom support */
@media (min-width: 768px) {
    /* Don't prevent user zoom */
    /* Never use: maximum-scale=1, user-scalable=no */
}

/* Font loading performance */
@font-face {
    font-family: 'CustomFont';
    src: url('/fonts/custom.woff2') format('woff2');
    font-display: swap;  /* Show fallback immediately */
}

/* WCAG 1.4.12: Text spacing must be customizable */
/* Users should be able to override these without breaking layout */
* {
    /* Line height: at least 1.5x font size */
    /* Paragraph spacing: at least 2x font size */
    /* Letter spacing: at least 0.12x font size */
    /* Word spacing: at least 0.16x font size */
}

/* Test with browser zoom at 200% */
/* Test with large text accessibility settings */

CSS Accessibility Best Practices

  • Never remove focus outlines without providing alternatives (use :focus-visible)
  • Maintain minimum 4.5:1 contrast for normal text, 3:1 for large text (WCAG AA)
  • Respect prefers-reduced-motion, disable decorative animations, keep functional ones
  • Support prefers-color-scheme, set color-scheme: light dark
  • Implement forced-colors: active for Windows High Contrast Mode
  • Use minimum 16px font size, 1.5 line-height, 45-75 character line length
  • Ensure text spacing is user-customizable (WCAG 1.4.12)
  • Don't use color alone to convey information, add icons or text
  • Test with keyboard navigation, screen readers, and zoom at 200%
  • Provide skip links, use semantic HTML, ensure proper heading hierarchy