Loading...

CSS Accessibility

You already ship layouts, motion, and modern toolkit tricks. Accessibility in CSS is the layer people feel: Can they see where the keyboard is? Can they read the text? Does a spinning animation make them sick? This session is not an HTML dump (landmarks, alt, ARIA). It is the style habits that keep focus, contrast, and motion usable.

Focus must stay visible

Keyboard users Tab through links and controls. If the focus ring disappears, they are lost. Prefer :focus-visible so mouse clicks stay calm while keyboard focus stays loud:

Visible keyboard focus with :focus-visible

css
:root {
--focus-ring: #2563eb;
}

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: 3px solid var(--focus-ring);
outline-offset: 3px;
}

.text-link:focus:not(:focus-visible) {
outline: none;
}

:focus-visible targets the cases where a focus ring helps (usually keyboard). Pair it with a high-contrast ring and a little outline-offset so the ring does not collide with borders.

Never outline: none without a replacement

Removing the default outline is fine only when you provide a clearer custom focus style. A bare *:focus { outline: none; } is a classic trap:

Replace outline — do not erase focus

css
/* Bad: keyboard users lose the only cue */
.bad-button:focus {
outline: none;
}

/* Good: custom ring that still shows focus */
.good-button:focus-visible {
outline: none;
box-shadow: 0 0 0 3px #fff, 0 0 0 6px #0f766e;
}

If you suppress the browser outline, something else must mark the focused control — box-shadow, border color, or a background shift that remains obvious on every theme.

Contrast is a CSS choice

Brand colors look sharp on a design board and muddy on a phone in sunlight. Treat foreground / background pairs as accessibility decisions, not decoration alone:

Contrast-safe text and surface tokens

css
:root {
--surface: #0f172a;
--text: #f8fafc;
--muted: #cbd5e1;
--accent: #38bdf8;
--accent-text: #0c4a6e;
}

.hero {
background: var(--surface);
color: var(--text);
}

.hero .lede {
color: var(--muted);
}

.hero .cta {
background: var(--accent);
color: var(--accent-text);
font-weight: 700;
}

Aim for strong contrast on body text and buttons. Decorative greys can sit softer on non-essential labels, but primary copy and controls need to stay readable. Check pairs with a contrast checker before you ship a palette.

Respect reduced motion

Animations and large transforms can cause discomfort. Wrap non-essential motion in prefers-reduced-motion:

Gate motion with prefers-reduced-motion

css
.banner {
transition: transform 0.4s ease, opacity 0.4s ease;
}

.banner.is-shown {
transform: translateY(0);
opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
.banner,
.banner.is-shown {
transition: none;
transform: none;
}

.spinner {
animation: none;
}
}

Users who ask the OS to reduce motion should still get the final UI state — just without the journey. Keep essential opacity or layout changes; drop looping spins and big slides.

High contrast and forced colors

Some environments override your palette (Windows High Contrast / forced-colors). Soften fragile decorations and keep borders real:

Stay usable under forced-colors

css
.card {
border: 1px solid #e2e8f0;
background: #fff;
box-shadow: 0 8px 24px rgb(15 23 42 / 12%);
}

@media (forced-colors: active) {
.card {
border: 1px solid CanvasText;
background: Canvas;
box-shadow: none;
color: CanvasText;
}

.card a {
color: LinkText;
}
}

System color keywords like Canvas, CanvasText, and LinkText help controls remain visible when the user — not your brand guide — owns the colors.

CSS-side habits

📊 CSS accessibility habits

Practice CSS habit Avoid
Keep focus findable :focus-visible + strong ring / shadow outline: none with no replacement
Readable type High-contrast text/background tokens Light grey on white for body copy
Kind motion @media (prefers-reduced-motion: reduce) Endless spins for everyone
Survive high contrast Real borders; forced-colors tweaks Shadow-only “edges”
Reachable controls Comfortable padding / hit area Tiny icon-only targets with no focus style

HTML still owns landmarks, labels, and alt text. CSS owns whether those controls stay perceivable once the page is styled.

Skip links and focus within

A skip link jumps past repeated chrome. Style it so it appears on focus. :focus-within can also highlight a whole group when a child is focused:

Skip link + focus-within group

css
.skip-link {
position: absolute;
left: 1rem;
top: -3rem;
padding: 0.5rem 0.75rem;
background: #111827;
color: #fff;
border-radius: 0.375rem;
z-index: 20;
}

.skip-link:focus-visible {
top: 1rem;
outline: 3px solid #fbbf24;
outline-offset: 2px;
}

.nav:focus-within {
box-shadow: inset 0 0 0 2px #2563eb;
}

The skip link stays off-screen until focused, then becomes obvious. The nav ring helps when Tab lands on a child link inside the bar.

Try it yourself

  1. Add a :focus-visible ring to links and buttons; Tab through the page and confirm you always see focus.
  2. Pick one low-contrast pair and fix it with darker text or a deeper surface.
  3. Wrap a transition or animation in prefers-reduced-motion: reduce and toggle the OS setting (or DevTools emulation).
  4. Under forced-colors emulation, ensure cards still show a real border.
  5. Style a skip link that appears only on keyboard focus.

Common mistakes

  • Global outline: none (or outline: 0) with no custom focus style
  • Pretty brand colors that fail contrast on body text and primary buttons
  • Large animations for everyone with no prefers-reduced-motion gate
  • Hiding a focusable control with opacity: 0 alone while it still receives Tab
  • Treating this CSS lesson as a full substitute for semantic HTML and ARIA

Summary

  • Keep keyboard focus visible — prefer :focus-visible and never erase outline without a stronger cue
  • Treat color contrast as a requirement for text and controls
  • Honor prefers-reduced-motion and harden styles for forced-colors
  • Style skip links and use :focus-within where a group needs a shared cue
  • Next: CSS Summary & Next Steps (summary) — wrap the course before the mini project

🧠 Test Your Knowledge

Ready to Start

Test Your Knowledge

Challenge yourself with this interactive quiz and see how well you understand the topic

❓
7
Questions
🎯
70%
To Pass
♾️
∞
Time
🔄
∞
Attempts

📝 Instructions

  • Read each question carefully
  • Select the best answer for each question
  • You can retake the quiz as many times as you want
  • Your progress will be shown at the top