Accessibility pattern · Form inputs

Password field

Show password is a toggle button: its name never changes, aria-pressed says whether the password is visible, and a click leaves focus in the field. The requirements update as you type but stay quiet; they are spoken when every one is met, or when the form is sent without them.

WCAG criteria
7
Keyboard rules
4
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Change password

Signed in as [email protected]

Your new password needs

  • Not yet: At least 12 characters
  • Not yet: Upper and lowercase letters
  • Not yet: At least one number
  • Not yet: Not your name

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
TabMoves from a password field to its Show button, then on to the next field.
Enter or SpaceOn Show, shows or hides that password; focus stays on the button.
EnterIn a field, sends the form. If something is wrong, focus moves to the first field that needs fixing.
Caps LockWhile typing in a password field, a warning appears under it when Caps Lock is on.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Focus reaches the new passwordNew password, password edit, required. Your new password needs: Not yet: At least 12 characters. Not yet: Upper and lowercase letters…
Focus reaches its Show buttonShow password, toggle button, not pressed
Space presses itPressed (the field now shows the password as text)
Typing meets the last requirementYour password meets every requirement
Caps Lock is on while typingCaps Lock is on
Enter is pressed in the new password with a requirement missingChoose a password that meets every requirement. Still needed: At least one number
The password is changedPassword updated

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<form class="ap-password-field" data-ap-password-field data-avoid="asha rao" novalidate aria-labelledby="pw-form-name">
  <div class="ap-password-field__top">
    <span class="ap-password-field__badge" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><circle cx="8" cy="15" r="4"/><path d="m11 12 8.5-8.5M16 7l2.5 2.5M14 9l2 2"/></svg></span>
    <div>
      <h3 class="ap-password-field__name" id="pw-form-name">Change password</h3>
      <p class="ap-password-field__sub">Signed in as [email protected]</p>
    </div>
  </div>

  <!-- Tells password managers which account the new password belongs to. -->
  <input type="email" name="username" autocomplete="username" value="[email protected]" hidden />

  <div class="ap-password-field__body">
    <div class="ap-field">
      <label class="ap-label" for="pw-current">Current password</label>
      <div class="ap-password-field__box">
        <input class="ap-input ap-password-field__input" id="pw-current" name="current" type="password" autocomplete="current-password" autocapitalize="off" spellcheck="false" required />
        <button type="button" class="ap-password-field__toggle" aria-label="Show password" aria-pressed="false" aria-controls="pw-current"><svg class="ap-password-field__eye" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z"/><circle cx="12" cy="12" r="3"/></svg><span>Show</span></button>
      </div>
      <p class="ap-password-field__caps" role="status"><svg class="ap-password-field__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 4 4.5 12H9v4h6v-4h4.5Z"/><path d="M9 20h6"/></svg><span data-ap-caps></span></p>
      <p class="ap-error ap-password-field__error" id="pw-current-error" hidden><svg class="ap-password-field__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M12 7.5v5"/><path d="M12 16.5h.01"/></svg><span class="ap-password-field__msg"></span></p>
    </div>

    <div class="ap-field">
      <label class="ap-label" for="pw-new">New password</label>
      <div class="ap-password-field__box">
        <input class="ap-input ap-password-field__input" id="pw-new" name="new" type="password" autocomplete="new-password" autocapitalize="off" spellcheck="false" required aria-describedby="pw-rules" />
        <button type="button" class="ap-password-field__toggle" aria-label="Show password" aria-pressed="false" aria-controls="pw-new"><svg class="ap-password-field__eye" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z"/><circle cx="12" cy="12" r="3"/></svg><span>Show</span></button>
      </div>
      <p class="ap-password-field__caps" role="status"><svg class="ap-password-field__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 4 4.5 12H9v4h6v-4h4.5Z"/><path d="M9 20h6"/></svg><span data-ap-caps></span></p>
      <p class="ap-error ap-password-field__error" id="pw-new-error" hidden><svg class="ap-password-field__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M12 7.5v5"/><path d="M12 16.5h.01"/></svg><span class="ap-password-field__msg"></span></p>
      <div class="ap-password-field__rules" id="pw-rules">
        <p class="ap-password-field__rules-name">Your new password needs</p>
        <ul class="ap-password-field__list">
          <li class="ap-password-field__rule" data-rule="length"><svg class="ap-password-field__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle class="ap-password-field__dot" cx="12" cy="12" r="8.5"/><path class="ap-password-field__tick" d="m8 12.5 2.8 2.8L16.5 9.5"/></svg><span class="ap-password-field__vh" data-ap-state>Not yet:</span> <span>At least 12 characters</span></li>
          <li class="ap-password-field__rule" data-rule="case"><svg class="ap-password-field__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle class="ap-password-field__dot" cx="12" cy="12" r="8.5"/><path class="ap-password-field__tick" d="m8 12.5 2.8 2.8L16.5 9.5"/></svg><span class="ap-password-field__vh" data-ap-state>Not yet:</span> <span>Upper and lowercase letters</span></li>
          <li class="ap-password-field__rule" data-rule="number"><svg class="ap-password-field__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle class="ap-password-field__dot" cx="12" cy="12" r="8.5"/><path class="ap-password-field__tick" d="m8 12.5 2.8 2.8L16.5 9.5"/></svg><span class="ap-password-field__vh" data-ap-state>Not yet:</span> <span>At least one number</span></li>
          <li class="ap-password-field__rule" data-rule="name"><svg class="ap-password-field__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle class="ap-password-field__dot" cx="12" cy="12" r="8.5"/><path class="ap-password-field__tick" d="m8 12.5 2.8 2.8L16.5 9.5"/></svg><span class="ap-password-field__vh" data-ap-state>Not yet:</span> <span>Not your name</span></li>
        </ul>
      </div>
    </div>
    <p class="ap-password-field__vh" aria-live="polite" data-ap-live></p>
  </div>

  <div class="ap-password-field__foot">
    <p class="ap-password-field__status" role="status"></p>
    <button type="submit" class="ap-btn ap-btn--primary">Update password</button>
  </div>
</form>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.3.1 Info and Relationships Level A

    Each label is tied to its input, and the checklist joins the new password's aria-describedby, so the rules are read with the field.

  • 1.3.5 Identify Input Purpose Level AA

    current-password and new-password tell browsers and password managers which value to fill and which to offer to generate.

  • 1.4.1 Use of Color Level A

    A met requirement changes its icon from an empty ring to a tick, and its hidden text from Not yet to Done; color only adds to that.

  • 3.3.1 Error Identification Level A

    A missing or weak password is marked aria-invalid and explained in text under the field.

  • 3.3.8 Accessible Authentication (Minimum) Level AA

    Pasting and password managers both work, and Show lets people check what they typed instead of retyping it.

  • 4.1.2 Name, Role, Value Level A

    The toggle is a button with aria-pressed and aria-controls, so its state and what it changes are exposed.

  • 4.1.3 Status Messages Level AA

    The Caps Lock warning, the all-met message and the result reach screen readers through polite live regions, without moving focus.

Usage

When to use it

Use it

  • Signing in, creating an account, and changing or resetting a password.
  • Any secret people type that they may need to check, like a PIN on a shared screen.

Use something else

  • One-time codes sent by text or email: use a one-time code field, which can be shown openly.
  • Requirements longer than a short list: simplify the policy rather than the checklist.

Common failures

How it usually goes wrong

  • A toggle whose label flips between Show and Hide

    Changing the name and adding aria-pressed makes "Hide password, pressed", a riddle. Here the name stays Show password and aria-pressed carries the state.

  • A checklist that talks on every keystroke

    A live region around the rules reads the whole list each time a letter is typed. Here it is quiet until every rule is met, or the form is sent.

  • Blocking paste

    Stopping paste breaks password managers and pushes people towards short, memorable passwords. Both fields here accept paste.

  • Missing autocomplete tokens

    Without current-password and new-password, browsers may fill the old password into the new field or never offer a strong one. Each field here says which it is.

  • Requirements shown only as a color change

    Turning a rule green is invisible to many. Each rule here changes its icon and has hidden text that says Done or Not yet.

  • A toggle that steals the caret

    If clicking Show moves focus to the button, people lose their place mid-word. A click here keeps focus and the caret in the field.

Notes

Building it

  • Switching the input's type between password and text is all Show does; the value and the caret position are kept.
  • Before the form is sent, both fields go back to type=password, so browsers offer to save the password and nothing is left on screen.
  • Caps Lock is read with getModifierState on each key press in a password field; the warning clears when Caps Lock goes off or focus leaves.
  • The hidden email input with autocomplete=username tells password managers which account the new password belongs to.
  • Check the same rules on the server; the checklist is help for people, not the security boundary.

Sources: WAI-ARIA Authoring Practices: Button · Understanding WCAG 2.2: Accessible Authentication (Minimum) · web.dev: Sign-in form best practices

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction