Screen Readers

Some content should exist for assistive technology without ever appearing on screen: extra context for an icon-only button, a label that clarifies an ambiguous link, a skip-navigation link that only sighted keyboard users need to see. display: none and visibility: hidden don't work for this — both remove the element from the accessibility tree along with the visual one. .sr-only solves that by hiding an element visually while leaving it fully readable by screen readers.

.sr-only

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

Every line here is load-bearing:

  • position: absolute pulls the element out of normal flow, so it can't push other content around.
  • width: 1px; height: 1px shrinks it to a single pixel instead of 0×0 — some older assistive technologies ignore zero-sized elements entirely.
  • margin: -1px cancels out that 1px box so it doesn't reintroduce any visible spacing.
  • overflow: hidden + clip: rect(0, 0, 0, 0) clips the content to nothing — clip is deprecated in favor of clip-path, but is kept here for the wider legacy screen-reader support it still guarantees.
  • white-space: nowrap stops long text from wrapping and inflating the 1px box back to something visible.
  • border-width: 0 strips any inherited border that could otherwise poke outside the 1px box.
<button class="btn">
  <svg aria-hidden="true">...</svg>
  <span class="sr-only">Delete item</span>
</button>

<a href="/pricing">
  View plans <span class="sr-only">(opens pricing page)</span>
</a>

In both examples, sighted users see a clean icon or a short link label, while screen reader users hear the fuller, unambiguous version.

.sr-only-focusable

Sometimes hidden content needs to become visible the moment it's reached — the classic case is a "Skip to main content" link, which should stay invisible for mouse users but appear the instant a keyboard user tabs to it.

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

The visually-hidden styles are wrapped in :not(:focus):not(:focus-within). As soon as the element itself is focused, or a descendant inside it is, the selector stops matching — and every one of those properties simply falls away, letting the element render normally, in-flow, at full size.

<body>
  <a href="#main-content" class="sr-only-focusable focus-ring">
    Skip to main content
  </a>

  <nav>...</nav>

  <main id="main-content">...</main>
</body>

Placed as the very first focusable element in <body>, this link is invisible on page load, appears the moment Tab reaches it, and jumps focus straight past the navigation when activated. Pairing it with Focus Ring gives it a visible, on-brand focus indicator once it appears.

.sr-only vs. aria-label

Both make text available to assistive technology without a visible label — the right choice depends on what else is on the element.

SituationRecommended approach
Icon-only button, no visible text at allaria-label — simplest, no extra markup
Visible text exists, but needs extra context for AT.sr-only — appends to the existing accessible name
Skip links, "jump to" navigation aids.sr-only-focusable — needs to become visible on focus
Content should be identical on screen and for AT, just styled differently.sr-only — avoids maintaining two separate strings

As a rule of thumb: reach for aria-label first when there's no visible text to build on, and reach for .sr-only when you're supplementing text that's already there.

FrontAlign