CSS Accessibility Features
Write inclusive CSS using focus styles, prefers-reduced-motion, forced-colors, skip links, and visually-hidden utilities.
Overview
Key Points
- •:focus-visible applies focus styles only for keyboard navigation, not mouse click
- •Never use outline: none without providing an alternative focus indicator
- •prefers-reduced-motion: reduce disables animations for users with vestibular disorders
- •.sr-only hides content visually while keeping it accessible to screen readers
- •forced-colors: active indicates Windows High Contrast Mode; use system color keywords there
- •prefers-contrast: more can be used for users who prefer higher contrast without forced colors
- •Color alone is never sufficient to convey information; always pair color with shape, text, or pattern
- •Focus management: after a modal opens, move focus inside it; after it closes, return focus to the trigger
Examples
Focus indicators for keyboard navigation
/* Global visible focus ring */
:focus-visible {
outline: 2.5px solid #6366f1;
outline-offset: 3px;
border-radius: 3px;
}
/* Custom focus ring for buttons */
.btn:focus-visible {
outline: 2.5px solid #6366f1;
outline-offset: 3px;
box-shadow: 0 0 0 5px rgba(99,102,241,0.2);
}
/* Links: ensure focus is distinct from hover */
a:focus-visible {
outline: 2.5px solid #6366f1;
outline-offset: 2px;
text-decoration: underline;
}
/* Form inputs */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: 2.5px solid #6366f1;
outline-offset: 0;
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99,102,241,0.15);
}:focus-visible is the modern approach: focus ring shows for keyboard but not mouse, matching native browser behavior.
Motion reduction
/* For users who prefer no motion */
@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;
}
}
/* Better approach: opt in to motion, not opt out */
.card {
/* No animation by default */
}
@media (prefers-reduced-motion: no-preference) {
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-4px);
}
}The opt-in approach (@media no-preference) is more explicit: animations only run when the user has not asked to reduce motion.
Screen reader utilities
/* Visually hidden but screen-reader accessible */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Reveal on focus (for skip navigation links) */
.sr-only-focusable:focus-visible {
position: static;
width: auto;
height: auto;
padding: 8px 16px;
margin: 0;
overflow: visible;
clip: auto;
white-space: normal;
}
/* Skip to main content link */
.skip-link {
position: absolute;
top: -100%;
left: 8px;
z-index: 9999;
background: #6366f1;
color: white;
padding: 8px 16px;
border-radius: 0 0 8px 8px;
font-weight: 600;
text-decoration: none;
transition: top 0.2s;
}
.skip-link:focus {
top: 0;
}High contrast mode (forced-colors)
/* Default styles */
.status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 99px;
background: #dcfce7;
color: #15803d;
}
.status-badge::before {
content: '';
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
}
/* High contrast: use system colors, ensure border */
@media (forced-colors: active) {
.status-badge {
background: Canvas;
color: CanvasText;
border: 2px solid CanvasText;
forced-color-adjust: none;
}
.status-badge::before {
background: Highlight;
}
}System color keywords (Canvas, CanvasText, ButtonFace, Highlight) map to OS high-contrast theme colors.
Common Mistakes
- Removing outline from :focus without providing an alternative (makes keyboard navigation invisible)
- Using only color to convey state (error states should also use an icon or text, not just red)
- Animating content without checking prefers-reduced-motion (can cause vertigo for vestibular disorder users)
- Using display: none or visibility: hidden for sr-only content (both hide from screen readers too)
Note
Test keyboard navigation regularly: tab through your UI and verify that you can always see where focus is. A user who cannot use a mouse depends on this entirely.
Exercises
Write CSS to style the HTML scaffold. The preview updates live — when it looks right, mark as done.
Completed: 0 / 2 exercises
Knowledge Check
Question 1 of 3