CSS for Accessibility
Accessible CSS makes your site usable by everyone, including people who use a keyboard, have weak eyesight, or feel ill from moving screens.
Why It Matters
Many visitors do not use a mouse. Some use only the keyboard, some use screen readers, and some zoom the page to 200%. Good CSS helps all of them, and it also helps everyone else: big buttons are easier to tap on a bumpy bus ride, too.
Never Remove Focus Without a Replacement
When you press Tab, the browser shows a ring around the focused link or button. Keyboard users need it to know where they are. A very common mistake is:
Example
/* Bad: keyboard users are now lost */
button:focus {
outline: none;
}:focus-visible
Designers sometimes dislike the ring appearing after a mouse click. :focus-visible solves this. The browser shows it when focus should be visible, such as when using the keyboard, and usually not after a mouse click on a button.
Example
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
a, button {
font-size: 1rem;
margin-right: 10px;
}
button {
background-color: #264de4;
color: white;
border: none;
padding: 10px 18px;
border-radius: 6px;
}
:focus-visible {
outline: 3px solid #f97316;
outline-offset: 3px;
}
</style>
</head>
<body>
<p>Click inside the page, then press the Tab key several times.</p>
<a href="#">Courses</a>
<button>Register</button>
<button>Contact</button>
</body>
</html>Try it Yourself »Clicking a button with the mouse shows no ring. Pressing Tab shows a clear orange ring with a small gap.
Use outline for focus styles, not border. An outline does not change the size of the element, so nothing jumps around when focus moves.
Respecting Reduced Motion
Some people get dizzy or sick from animations, parallax and smooth scrolling. They can switch on "Reduce motion" in their phone or computer settings. CSS can read this with prefers-reduced-motion.
| Query | Matches when |
|---|---|
@media (prefers-reduced-motion: reduce) | The user asked for less motion |
@media (prefers-reduced-motion: no-preference) | The user has not asked |
The safest pattern is to add motion only for people who have not asked for less:
Example
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.badge {
display: inline-block;
background-color: crimson;
color: white;
padding: 8px 14px;
border-radius: 20px;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@media (prefers-reduced-motion: no-preference) {
.badge {
animation: bounce 1s ease-in-out infinite;
}
html {
scroll-behavior: smooth;
}
}
</style>
</head>
<body>
<span class="badge">Admissions open</span>
<p>Turn on "Reduce motion" in your device settings and run this again: the badge stops bouncing.</p>
</body>
</html>Try it Yourself »A short way to switch off almost all motion on an existing site:
Example
@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;
}
}Colour Contrast
Text must stand out clearly from its background. The WCAG guidelines ask for a contrast ratio of at least:
| Text | Minimum ratio |
|---|---|
| Normal text | 4.5 : 1 |
| Large text (about 24px, or 19px bold) | 3 : 1 |
| Icons and input borders | 3 : 1 |
Light grey text on a white background often fails. Browser developer tools (F12) show the contrast ratio when you inspect a colour.
Never show meaning with colour alone. A red border for an error is not enough for colour-blind users. Add an icon or a message such as "Email is required".
Readable Text and Zoom
- Use
remfor font sizes so the user's browser font setting is respected (see CSS Units) - Keep body text at least
1rem(16px) - Use
line-heightof about1.5for paragraphs - Limit line length with
max-width: 65ch - Never block zooming in the viewport meta tag (do not use
user-scalable=no)
Bigger Tap Targets
Small links are hard to tap, especially for older people. Aim for buttons and links at least 44 by 44 pixels.
Example
.icon-button {
min-width: 44px;
min-height: 44px;
}Visually Hidden Text
Sometimes a button shows only an icon, like a search glass. Screen readers still need a text label. This common class hides text on screen but keeps it for screen readers:
Example
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}Example
<button><span aria-hidden="true">🔍</span><span class="visually-hidden">Search</span></button>Do not use display: none for this, because it hides the text from screen readers too.
Exercise
Exercise
Which media query lets you turn off animations for users who asked their device for less motion?
