CSS Accessibility — Complete Guide
In this tutorial, you will learn about CSS Accessibility. We cover key concepts, practical examples, and best practices to help you master this topic.
CSS accessibility covers visible focus indicators, prefers-reduced-motion, hiding content accessibly, avoiding conveying information through color alone, print styles, and ensuring text scales properly with browser zoom.
What You'll Learn
- Visible focus indicators that meet WCAG requirements
- Respecting user motion preferences (prefers-reduced-motion)
- Hiding content accessibly versus visually
- Avoiding information conveyed only through color
- Responsive text and zoom-safe layouts
- Print styles for accessibility
Why It Matters
- CSS controls the visual presentation that many users depend on
- Poor CSS choices can make content invisible or unusable
- CSS media features let you respect user preferences
- Many accessibility issues are CSS issues, not HTML or JS issues
Real-World Use
- A user with vestibular disorder enables reduced motion in OS settings
- A keyboard user relies on visible focus indicators to navigate
- A user with low vision zooms the browser to 200 percent
- A user prints a page for offline reading
flowchart LR A[CSS Accessibility] --> B[Focus Indicators] A --> C[Motion Preferences] A --> D[Color Independence] A --> E[Text Scaling] A --> F[Print Styles] B --> G[focus-visible] C --> H[prefers-reduced-motion] D --> I[Not only color] E --> J[Relative units]
Focus Indicators
A visible focus indicator is essential for keyboard users. Without it, users cannot tell which element has focus. WCAG 2.2 requires focus indicators with minimum size and contrast.
Code Example: Accessible Focus Styles
/* Default focus (visible for all users) */
:focus {
outline: 2px solid #0056B3;
outline-offset: 2px;
}
/* Focus visible (only for keyboard users) */
:focus-visible {
outline: 3px solid #0056B3;
outline-offset: 2px;
border-radius: 4px;
}
/* Never do this: */
/* :focus { outline: none; } - removes focus indicator for ALL users */
/* Custom focus for specific elements */
a:focus-visible {
outline: 3px solid #0056B3;
outline-offset: 3px;
text-decoration: underline;
color: #0056B3;
}
button:focus-visible {
outline: 3px solid #0056B3;
outline-offset: 2px;
box-shadow: 0 0 0 3px #0056B3;
}
input:focus-visible {
outline: 3px solid #0056B3;
outline-offset: 1px;
border-color: #0056B3;
}
Expected output: Mouse users see no focus ring (or a subtle one). Keyboard users see a prominent blue outline when tabbing. The outline meets the 3:1 minimum contrast requirement.
Motion Sensitivity
Some users have vestibular disorders that cause dizziness, nausea, or pain from animations. CSS provides a media feature to respect these preferences.
Code Example: Respecting Motion Preferences
/* Base animations */
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.modal-enter {
animation: slideIn 0.3s ease-out;
}
/* Reduced motion: remove all animations */
@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;
}
}
/* Alternative: provide a static state */
@media (prefers-reduced-motion: reduce) {
.modal-enter {
animation: none;
opacity: 1;
transform: none;
}
}
/* Pause button for auto-rotating carousels */
.carousel-auto {
animation: slide 5s infinite;
}
.carousel-paused {
animation-play-state: paused;
}
Expected output: Users who enable "Reduce motion" in their OS settings see no animations. Elements appear in their final state instantly. The carousel has a pause button for additional control.
Hiding Content Accessibly
There are different ways to hide content, each with different effects on accessibility:
/* 1. Completely hidden (removed from accessibility tree) */
.hidden-display {
display: none;
}
.hidden-visibility {
visibility: hidden;
}
/* 2. Visually hidden but accessible to screen readers */
.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;
}
/* 3. Visible but hidden from screen readers */
.aria-hidden {
aria-hidden: true;
/* CSS alone cannot do this. Use aria-hidden="true" in HTML */
}
/* 4. Off-screen (focusable but not visible) */
.off-screen {
position: absolute;
left: -9999px;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
}
.off-screen:focus {
position: static;
width: auto;
height: auto;
}
Expected output: display: none removes from both visual and accessibility tree. sr-only keeps content for screen readers but hides visually. off-screen is useful for skip links that appear when focused.
Information Through Color Alone
WCAG requires that information is not conveyed through color alone. Add patterns, text, or icons alongside color:
/* Bad: only color indicates status */
.status-bad { color: green; }
.error-bad { color: red; }
/* Good: color + text + icon */
.status {
display: flex;
align-items: center;
gap: 0.5rem;
}
.status::before {
content: "";
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
}
.status.success { color: #2E7D32; }
.status.success::before { background: #2E7D32; }
.status.success::after { content: " (Success)"; }
.status.error { color: #C62828; }
.status.error::before { background: #C62828; }
.status.error::after { content: " (Error)"; }
/* Chart patterns instead of color only */
.chart-bar-1 { background: #0072B2; } /* Blue + label */
.chart-bar-2 { background: #D55E00; } /* Orange + label */
.chart-bar-3 { background: #009E73; } /* Green + label */
Expected output: Colorblind users can distinguish status indicators by the text and icon, not just color. Chart bars have labels that do not rely on color perception.
Text Scaling and Zoom
Users with low vision often zoom the browser to 150-200 percent. Your CSS must support this:
/* Use relative units for text */
html {
font-size: 100%; /* Respect user's default font size */
}
body {
font-size: 1rem; /* 16px default, scales with zoom */
line-height: 1.5; /* Proportional to text size */
}
h1 { font-size: 2rem; }
h2 { font-size: 1.5rem; }
/* Avoid fixed sizes that break at zoom */
/* Bad: */ .bad-container { width: 960px; }
/* Good: */ .good-container { max-width: 60rem; padding: 1rem; }
/* Ensure text does not overflow at 200% zoom */
.container {
max-width: 100%;
overflow-wrap: break-word;
word-wrap: break-word;
hyphens: auto;
}
/* Minimum touch target size (44x44px) */
button, a, input, select {
min-height: 44px;
min-width: 44px;
}
/* Responsive spacing */
.content {
padding: clamp(1rem, 5vw, 3rem);
gap: clamp(0.5rem, 2vw, 2rem);
}
Expected output: At 200 percent browser zoom, all text remains readable without horizontal scrolling. Containers resize fluidly. Touch targets stay at least 44x44 CSS pixels.
Common Mistakes
- Removing focus outlines with
outline: none— This makes it impossible for keyboard users to see where focus is. Always provide an alternative focus style. - Animations without reduced-motion support — Users with vestibular disorders can experience pain from auto-playing animations.
- Hiding content with
display: nonewhen it should be screen-reader only — The.sr-onlypattern keeps content accessible while hiding it visually. - Conveying information with color only — Red/green status indicators are invisible to colorblind users. Always pair with text or icons.
- Using px for font sizes — px units do not scale with browser text-size settings. Use rem or em for text.
- Fixed-width layouts that break at zoom — A fixed 960px container causes horizontal scrolling at 200 percent zoom on a 1920px screen.
- Touch targets smaller than 44x44px — Small links and buttons are difficult to tap for users with motor disabilities.
Practice Questions
- What is the difference between
:focusand:focus-visible?:focusapplies whenever an element has focus.:focus-visibleonly applies when the browser determines focus should be visible, typically for keyboard users. - What media feature respects user motion preferences?
prefers-reduced-motion. It returns "reduce" when the user has enabled reduced motion in their OS settings. - How do you hide content visually but keep it accessible to screen readers? Use the
.sr-onlypattern: position absolute, 1x1 pixel size, overflow hidden, clip rect. - Why should you use relative units (rem, em) instead of pixels for text? Relative units respect the user's browser font size setting and scale properly with zoom.
- Challenge: Create a CSS-only notification system that: appears with a slide-in animation (that respects prefers-reduced-motion), uses color + icon + text for status (success, warning, error), has a visible focus indicator on the dismiss button, uses relative units, and maintains readability at 200 percent zoom.
FAQ
Mini Project
Build a notification/toast system that demonstrates all CSS accessibility principles. Include: 3 notification types (success, warning, error) using color + icon + text, animated entrance/exit that respects prefers-reduced-motion, visible focus indicators on action buttons, 44px minimum touch targets, relative units for all sizing, proper content hiding for screen-reader-only text, and a "clear all" button. Test at 200 percent zoom, with keyboard navigation, and with prefers-reduced-motion enabled in browser DevTools. Document the CSS accessibility features.
What's Next
Continue with Lesson 21: Animation and Motion Accessibility to learn about creating safe animations for users with vestibular disorders.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro