Skip to content

Screen Reader Utilities

Utilities for hiding content visually while keeping it accessible to screen readers.

ClassPurpose
af-sr-onlyVisually hidden. Accessible to screen readers.
af-sr-only-focusableHidden until focused. Use for skip links.

Hides an element visually but keeps it in the accessibility tree. Screen readers will still announce it.

<!-- Icon button with accessible label -->
<button class="af-btn af-btn-icon">
  <span class="af-sr-only">
    Close
  </span>
  ×
</button>
<!-- Accessible table caption -->
<table>
  <caption class="af-sr-only">
    Quarterly revenue by region
  </caption>
  ...
</table>
  • Icon-only buttons that need a text label
  • Decorative images with adjacent visible text
  • Table captions when a visual heading already exists
  • Additional context for screen reader users

Hidden until the element receives focus. This is the standard pattern for skip links.

<!-- Skip to main content link -->
<a href="#main-content" class="af-sr-only-focusable">
  Skip to main content
</a>
<header>
  ...
</header>
<main id="main-content">
  ...
</main>

The skip link is invisible during normal browsing. When a keyboard user presses Tab, the link appears, letting them jump past navigation directly to the main content.

When focused, the element becomes visible with its default styles. You can style it further:

.af-sr-only-focusable:focus {
  position: fixed;
  top: var(--af-space-2);
  left: var(--af-space-2);
  z-index: 9999;
  padding: var(--af-space-2) var(--af-space-4);
  background: var(--af-color-surface-primary);
  border: var(--af-border-width) solid var(--af-color-border);
  border-radius: var(--af-radius-md);
  box-shadow: var(--af-shadow-lg);
}
.af-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border-width: 0;
}

.af-sr-only-focusable:not(:focus):not(:focus-within) {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border-width: 0;
}