|
| 1 | +/* Focus Management System - WCAG 2.4.7 & 1.4.11 Compliant */ |
| 2 | + |
| 3 | +/* |
| 4 | + * Focus indicators with proper contrast ratios (minimum 3:1) |
| 5 | + * Uses focus-visible for better UX (keyboard navigation only) |
| 6 | + */ |
| 7 | + |
| 8 | +:root { |
| 9 | + /* Focus indicator colors with high contrast */ |
| 10 | + --focus-outline-color-light: #0066cc; /* Blue with high contrast */ |
| 11 | + --focus-outline-color-dark: #4da6ff; /* Lighter blue for dark mode */ |
| 12 | + --focus-outline-width: 2px; |
| 13 | + --focus-outline-offset: 2px; |
| 14 | + --focus-outline-style: solid; |
| 15 | + |
| 16 | + /* Alternative focus colors for different contexts */ |
| 17 | + --focus-outline-warning: #ff6b00; /* Orange for warnings/errors */ |
| 18 | + --focus-outline-success: #00a651; /* Green for success states */ |
| 19 | + |
| 20 | + /* Box shadow focus for cases where outline doesn't work well */ |
| 21 | + --focus-shadow-light: 0 0 0 2px #ffffff, 0 0 0 4px #0066cc; |
| 22 | + --focus-shadow-dark: 0 0 0 2px #0a0a0a, 0 0 0 4px #4da6ff; |
| 23 | +} |
| 24 | + |
| 25 | +[data-theme='dark'] { |
| 26 | + --focus-outline-color-light: var(--focus-outline-color-dark); |
| 27 | + --focus-shadow-light: var(--focus-shadow-dark); |
| 28 | +} |
| 29 | + |
| 30 | +/* Remove default focus styles only when we provide alternatives */ |
| 31 | +*:focus { |
| 32 | + outline: auto; |
| 33 | +} |
| 34 | + |
| 35 | +/* Apply focus-visible for keyboard navigation */ |
| 36 | +*:focus-visible { |
| 37 | + outline: var(--focus-outline-width) var(--focus-outline-style) var(--focus-outline-color-light); |
| 38 | + outline-offset: var(--focus-outline-offset); |
| 39 | +} |
| 40 | + |
| 41 | +/* Enhanced focus for interactive elements */ |
| 42 | +a:focus-visible, |
| 43 | +button:focus-visible, |
| 44 | +[role='button']:focus-visible, |
| 45 | +[role='link']:focus-visible, |
| 46 | +[tabindex]:focus-visible { |
| 47 | + outline: var(--focus-outline-width) var(--focus-outline-style) var(--focus-outline-color-light); |
| 48 | + outline-offset: var(--focus-outline-offset); |
| 49 | +} |
| 50 | + |
| 51 | +/* Form controls need special handling */ |
| 52 | +input:focus-visible, |
| 53 | +textarea:focus-visible, |
| 54 | +select:focus-visible { |
| 55 | + outline: var(--focus-outline-width) var(--focus-outline-style) var(--focus-outline-color-light); |
| 56 | + outline-offset: 1px; /* Tighter offset for form controls */ |
| 57 | +} |
| 58 | + |
| 59 | +/* Checkbox and radio buttons */ |
| 60 | +input[type='checkbox']:focus-visible, |
| 61 | +input[type='radio']:focus-visible { |
| 62 | + outline: var(--focus-outline-width) var(--focus-outline-style) var(--focus-outline-color-light); |
| 63 | + outline-offset: 2px; |
| 64 | +} |
| 65 | + |
| 66 | +/* For elements where outline might be cut off, use box-shadow */ |
| 67 | +.focus-shadow:focus-visible { |
| 68 | + outline: none; |
| 69 | + box-shadow: var(--focus-shadow-light); |
| 70 | +} |
| 71 | + |
| 72 | +/* High contrast mode support */ |
| 73 | +@media (prefers-contrast: high) { |
| 74 | + *:focus-visible { |
| 75 | + outline-width: 3px; |
| 76 | + outline-color: highlight; |
| 77 | + } |
| 78 | +} |
| 79 | + |
| 80 | +/* Focus for elements that might have complex backgrounds */ |
| 81 | +.focus-high-contrast:focus-visible { |
| 82 | + outline: 3px solid var(--focus-outline-color-light); |
| 83 | + outline-offset: 2px; |
| 84 | + background-color: rgba(255, 255, 255, 0.9); |
| 85 | + |
| 86 | + [data-theme='dark'] & { |
| 87 | + background-color: rgba(0, 0, 0, 0.9); |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +/* Skip link focus (for screen reader users) */ |
| 92 | +.skip-link:focus-visible { |
| 93 | + position: absolute; |
| 94 | + top: 10px; |
| 95 | + left: 10px; |
| 96 | + z-index: 9999; |
| 97 | + padding: 8px 16px; |
| 98 | + background: var(--focus-outline-color-light); |
| 99 | + color: white; |
| 100 | + text-decoration: none; |
| 101 | + border-radius: 4px; |
| 102 | + outline: 2px solid white; |
| 103 | + outline-offset: 2px; |
| 104 | +} |
| 105 | + |
| 106 | +/* Ensure focus is visible even when using custom components */ |
| 107 | +.custom-focus:focus-visible { |
| 108 | + position: relative; |
| 109 | +} |
| 110 | + |
| 111 | +.custom-focus:focus-visible::after { |
| 112 | + content: ''; |
| 113 | + position: absolute; |
| 114 | + top: -2px; |
| 115 | + right: -2px; |
| 116 | + bottom: -2px; |
| 117 | + left: -2px; |
| 118 | + border: var(--focus-outline-width) var(--focus-outline-style) var(--focus-outline-color-light); |
| 119 | + border-radius: inherit; |
| 120 | + pointer-events: none; |
| 121 | +} |
| 122 | + |
| 123 | +/* Focus indicators that work well with rounded corners */ |
| 124 | +.focus-rounded:focus-visible { |
| 125 | + outline: var(--focus-outline-width) var(--focus-outline-style) var(--focus-outline-color-light); |
| 126 | + outline-offset: var(--focus-outline-offset); |
| 127 | + border-radius: inherit; |
| 128 | +} |
| 129 | + |
| 130 | +/* Error state focus */ |
| 131 | +.focus-error:focus-visible { |
| 132 | + outline-color: var(--focus-outline-warning); |
| 133 | +} |
| 134 | + |
| 135 | +/* Success state focus */ |
| 136 | +.focus-success:focus-visible { |
| 137 | + outline-color: var(--focus-outline-success); |
| 138 | +} |
| 139 | + |
| 140 | +/* Reduced motion support */ |
| 141 | +@media (prefers-reduced-motion: no-preference) { |
| 142 | + *:focus-visible { |
| 143 | + transition: outline-color 0.15s ease-in-out; |
| 144 | + } |
| 145 | +} |
0 commit comments