Skip to Content
Component ExamplesPassword Show/Hide

Overview

Password show/hide toggles let users check what they’ve typed in a password field. The pattern is deceptively complex for accessibility: toggling the input type between password and text must be communicated to screen reader users without reading the actual password aloud. An icon-only button with no accessible name, no state management, and no status announcement leaves assistive technology users completely in the dark — or worse, exposes their password through a live region.

WCAG Criteria:

Key requirements:

  • The toggle button must have an accessible name (“Show password”) — icon-only buttons with no label are invisible to screen readers
  • Use aria-pressed to communicate whether the password is currently shown or hidden
  • Announce the state change (“Password is now visible” / “Password is now hidden”) via an aria-live="polite" region
  • Never place the password value itself inside a live region — that would read it aloud
  • Never change both the label AND aria-pressed at the same time — pick one mechanism for communicating state
  • Return focus to the password input after toggling so the user can continue typing
  • The GOV.UK pattern is the gold standard for this interaction

Password Toggle

Icon-Only Eye Button vs. Accessible Show/Hide Toggle

Inaccessible
<!-- Icon-only button — no name, no state, no announcement --> <div> <div>Password</div> <input id="pw-bad-input" type="password" /> <button onclick=" var i = document.getElementById('pw-bad-input'); i.type = i.type === 'password' ? 'text' : 'password'; "> &#128065; <!-- eye icon only --> </button> </div>
Live Preview
Password
Accessible
<!-- Labeled button + aria-pressed + live region announcement --> <label for="pw-good-input">Password</label> <input id="pw-good-input" type="password" /> <button type="button" aria-pressed="false" onclick=" var input = document.getElementById('pw-good-input'); var pressed = this.getAttribute('aria-pressed') === 'true'; var sr = document.getElementById('pw-sr'); if (!pressed) { input.type = 'text'; this.setAttribute('aria-pressed', 'true'); sr.textContent = 'Password is now visible'; } else { input.type = 'password'; this.setAttribute('aria-pressed', 'false'); sr.textContent = 'Password is now hidden'; } input.focus(); "> Show password </button> <!-- Announces state change without reading the password --> <span id="pw-sr" aria-live="polite" class="sr-only"></span>
Live Preview

What’s wrong with the icon-only eye button?

  • No accessible name — screen readers announce “button” with no indication of what it does
  • No aria-pressed state — screen readers cannot tell whether the password is currently visible or hidden
  • No status announcement — toggling the input type produces no feedback for assistive technology users
  • The eye emoji/icon is the only affordance — completely invisible to screen readers
  • The <div>Password</div> is not a <label> — the input has no programmatic label
  • Focus stays on the button after toggle, forcing the user to Tab back to the input

What the screen reader announces:

VersionAnnouncement
Inaccessible (icon-only button)“button” (no name, no state, no feedback on toggle)
Accessible (aria-pressed + live region)“Show password, toggle button, not pressed” then on toggle: “Password is now visible”

Critical: never announce the password itself. If you put the password input’s value into a live region, the screen reader will read the password aloud — a serious privacy and security issue, especially in shared spaces.

Why the label stays “Show password”:

  • The aria-pressed attribute communicates the current state (pressed = visible, not pressed = hidden)
  • Changing the label to “Hide password” while also flipping aria-pressed sends conflicting signals
  • A stable label helps speech recognition users who need to say the button name consistently

Resources