Expert10 min read

CSS for Screen Readers: Accessible Hiding and ARIA Patterns

10 min read
329 words
15 sections14 code blocks

Why CSS Accessibility Matters

CSS does more than make websites look good — it directly impacts whether people with disabilities can use your site. Screen readers, keyboard navigation, and assistive technologies all rely on proper CSS implementation to function correctly.

Visually Hidden but Accessible

The most important accessibility pattern in CSS. This hides content visually while keeping it readable by screen readers:

CSS
.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;
}

When to Use sr-only

HTML
<!-- Icon-only button needs accessible label -->
<button>
  <svg><!-- close icon --></svg>
  <span class="sr-only">Close menu</span>
</button>

<!-- Skip navigation link -->
<a href="#main-content" class="sr-only sr-only--focusable">
  Skip to main content
</a>

<!-- Table that needs context -->
<table>
  <caption class="sr-only">Monthly sales data for 2024</caption>
</table>

sr-only That Becomes Visible on Focus

CSS
.sr-only--focusable:focus,
.sr-only--focusable:active {
  position: static;
  width: auto;
  height: auto;
  padding: 0.5rem 1rem;
  margin: 0;
  overflow: visible;
  clip: auto;
  white-space: normal;
}

display: none vs visibility: hidden vs sr-only

Understanding which hiding method to use is critical:

CSS
/* Hidden from EVERYONE (visual + screen readers) */
.hidden { display: none; }

/* Hidden from EVERYONE (visual + screen readers), space preserved */
.invisible { visibility: hidden; }

/* Hidden from visual users ONLY, screen readers can read it */
.sr-only { /* the sr-only pattern above */ }

/* Hidden from visual users, still interactive */
.transparent { opacity: 0; }
  • Use display: none for content that should be completely hidden
  • Use visibility: hidden when you need to preserve layout space
  • Use sr-only for screen-reader-only content
  • Use opacity: 0 for elements you want to animate into view

Skip links help keyboard users jump past repetitive navigation:

CSS
.skip-link {
  position: absolute;
  top: -100%;
  left: 0;
  background: #2c3e50;
  color: white;
  padding: 0.75rem 1.5rem;
  z-index: 10000;
  font-size: 1rem;
  text-decoration: none;
  transition: top 0.2s;
}

.skip-link:focus {
  top: 0;
}
HTML
<body>
  <a href="#main-content" class="skip-link">Skip to main content</a>
  <nav><!-- Long navigation --></nav>
  <main id="main-content"><!-- Page content --></main>
</body>

Focus Styles

Never remove focus outlines without providing an alternative:

CSS
/* BAD: Removes focus indicator completely */
*:focus { outline: none; }

/* GOOD: Custom focus style */
*:focus-visible {
  outline: 2px solid #3498db;
  outline-offset: 2px;
}

/* GOOD: Remove outline only for mouse clicks */
*:focus:not(:focus-visible) {
  outline: none;
}

Enhanced Focus Styles

CSS
a:focus-visible {
  outline: 2px solid #3498db;
  outline-offset: 3px;
  border-radius: 2px;
}

button:focus-visible {
  outline: 2px solid #3498db;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(52, 152, 219, 0.3);
}

input:focus-visible {
  border-color: #3498db;
  box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.25);
  outline: none;
}

prefers-reduced-motion

Some users experience motion sickness or discomfort from animations. The prefers-reduced-motion media query respects their system settings:

CSS
/* Default: animations enabled */
.card {
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}

/* Reduced motion: remove or minimize animations */
@media (prefers-reduced-motion: reduce) {
  .card {
    transition: none;
  }

  .card:hover {
    transform: none;
  }

  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

prefers-color-scheme

Respect the user's dark mode preference:

CSS
:root {
  --bg: #ffffff;
  --text: #333333;
  --border: #e0e0e0;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1a1a1a;
    --text: #e0e0e0;
    --border: #333333;
  }
}

body {
  background: var(--bg);
  color: var(--text);
}

Sufficient Color Contrast

Ensure text has enough contrast against its background. WCAG guidelines require:

  • Normal text: Minimum contrast ratio of 4.5:1
  • Large text (18px+ bold or 24px+ regular): Minimum 3:1
CSS
/* GOOD contrast */
.text-good {
  color: #333333;          /* Dark gray on white = 12.6:1 */
  background: #ffffff;
}

/* BAD contrast */
.text-bad {
  color: #aaaaaa;          /* Light gray on white = 2.3:1 */
  background: #ffffff;
}

Focus Order and tabindex

CSS can affect the visual order (using flexbox order, grid placement), but tab order follows the DOM. Ensure visual order matches DOM order:

CSS
/* Careful: visual order differs from tab order */
.item-1 { order: 3; }
.item-2 { order: 1; }
.item-3 { order: 2; }
/* User sees: 2, 3, 1 but tabs through: 1, 2, 3 */

This can confuse keyboard users. Only reorder visually when the tab order still makes sense.

ARIA State Styling

Style elements based on their ARIA attributes:

CSS
/* Style expanded/collapsed state */
[aria-expanded="true"] .icon {
  transform: rotate(180deg);
}

[aria-expanded="false"] + .panel {
  display: none;
}

/* Style current page in navigation */
[aria-current="page"] {
  font-weight: bold;
  border-bottom: 2px solid #3498db;
}

/* Style disabled state */
[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* Style invalid form fields */
[aria-invalid="true"] {
  border-color: #e74c3c;
}

Touch Target Size

Ensure interactive elements are large enough to tap on mobile (minimum 44x44 pixels):

CSS
button, a, input, select {
  min-height: 44px;
  min-width: 44px;
}

/* Small links need larger click area */
.small-link {
  position: relative;
  padding: 0.5rem;
}

.small-link::after {
  content: '';
  position: absolute;
  inset: -8px;
}

Summary

Accessible CSS goes beyond visual design. Use the sr-only pattern for screen-reader-only content, always provide visible focus indicators, respect user preferences with prefers-reduced-motion and prefers-color-scheme, ensure sufficient color contrast, and style based on ARIA attributes. These patterns make your website usable for everyone, including users with visual, motor, and cognitive disabilities.