Accessibility pattern · Forms and flows

Sign up

Name, email and a new password, each marked up so browsers and password managers can fill them in, and no field that asks for the same thing twice. The password checklist updates as you type, but it speaks only when a requirement changes, once you pause, and sending the form with problems moves focus to a summary of 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.

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 through the fields, from the password to its Show button, then on to the checkbox and Create account.
Enter or SpaceOn Show, shows or hides the password as text; focus stays on the button.
SpaceOn the checkbox, ticks or clears it.
EnterIn a field, sends the form. With problems, focus moves to the summary; a link in it moves focus to its field.

Screen readers

What it announces

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

WhenExpected announcement
Focus reaches the passwordPassword, password edit, required. Your password needs: Not yet: At least 12 characters. Not yet: Not a commonly used password…
Typing pauses after the twelfth characterDone: At least 12 characters
Typing pauses after every rule is metDone: Not your name or email address. Your password meets every requirement.
Focus reaches ShowShow password, toggle button, not pressed
Create account is pressed with problemsThere is a problem, group. Enter your full name. Tick the box to agree to the terms of use.
Every field is right and the form is sentAccount created. Check your email for a link to confirm it.

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-sign-up" data-ap-sign-up novalidate aria-labelledby="su-form-name">
  <div class="ap-sign-up__top">
    <h3 class="ap-sign-up__name" id="su-form-name">Create an account</h3>
    <p class="ap-sign-up__sub">Every field is required. Already have an account? <a class="ap-sign-up__link" href="#su-sign-in">Sign in</a></p>
  </div>

  <div class="ap-sign-up__summary" id="su-summary" role="group" aria-labelledby="su-summary-name" aria-describedby="su-summary-list" tabindex="-1" hidden>
    <h4 class="ap-sign-up__summary-name" id="su-summary-name"><svg class="ap-sign-up__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>There is a problem</h4>
    <ul class="ap-sign-up__summary-list" id="su-summary-list"></ul>
  </div>

  <div class="ap-sign-up__body">
    <div class="ap-field ap-sign-up__field">
      <label class="ap-label" for="su-name">Full name</label>
      <p class="ap-error ap-sign-up__error" id="su-name-error" hidden><svg class="ap-sign-up__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-sign-up__vh">Error:</span> <span class="ap-sign-up__msg"></span></p>
      <input class="ap-input" id="su-name" name="name" type="text" autocomplete="name" spellcheck="false" required />
    </div>

    <div class="ap-field ap-sign-up__field">
      <label class="ap-label" for="su-email">Email address</label>
      <p class="ap-hint" id="su-email-hint">You sign in with this. We send a link to confirm it.</p>
      <p class="ap-error ap-sign-up__error" id="su-email-error" hidden><svg class="ap-sign-up__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-sign-up__vh">Error:</span> <span class="ap-sign-up__msg"></span></p>
      <input class="ap-input" id="su-email" name="email" type="email" autocomplete="email" autocapitalize="none" spellcheck="false" required aria-describedby="su-email-hint" />
    </div>

    <div class="ap-field ap-sign-up__field">
      <label class="ap-label" for="su-password">Password</label>
      <p class="ap-error ap-sign-up__error" id="su-password-error" hidden><svg class="ap-sign-up__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-sign-up__vh">Error:</span> <span class="ap-sign-up__msg"></span></p>
      <div class="ap-sign-up__box">
        <input class="ap-input ap-sign-up__secret" id="su-password" name="password" type="password" autocomplete="new-password" autocapitalize="none" spellcheck="false" required aria-describedby="su-rules" />
        <button type="button" class="ap-sign-up__show" aria-label="Show password" aria-pressed="false" aria-controls="su-password" data-ap-show>
          <svg class="ap-sign-up__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"/><path class="ap-sign-up__slash" d="m4 4 16 16"/></svg>
          <span>Show</span>
        </button>
      </div>
      <div class="ap-sign-up__rules" id="su-rules">
        <p class="ap-sign-up__rules-name">Your password needs</p>
        <ul class="ap-sign-up__list">
          <li class="ap-sign-up__rule" data-ap-rule="length"><svg class="ap-sign-up__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle class="ap-sign-up__ring" cx="12" cy="12" r="8.5"/><path class="ap-sign-up__tick" d="m8 12.5 2.8 2.8L16.5 9.5"/></svg><span class="ap-sign-up__vh" data-ap-state>Not yet:</span> <span>At least 12 characters</span></li>
          <li class="ap-sign-up__rule" data-ap-rule="common"><svg class="ap-sign-up__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle class="ap-sign-up__ring" cx="12" cy="12" r="8.5"/><path class="ap-sign-up__tick" d="m8 12.5 2.8 2.8L16.5 9.5"/></svg><span class="ap-sign-up__vh" data-ap-state>Not yet:</span> <span>Not a commonly used password</span></li>
          <li class="ap-sign-up__rule" data-ap-rule="personal"><svg class="ap-sign-up__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle class="ap-sign-up__ring" cx="12" cy="12" r="8.5"/><path class="ap-sign-up__tick" d="m8 12.5 2.8 2.8L16.5 9.5"/></svg><span class="ap-sign-up__vh" data-ap-state>Not yet:</span> <span>Not your name or email address</span></li>
        </ul>
      </div>
    </div>

    <div class="ap-field ap-sign-up__field">
      <p class="ap-error ap-sign-up__error" id="su-terms-error" hidden><svg class="ap-sign-up__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-sign-up__vh">Error:</span> <span class="ap-sign-up__msg"></span></p>
      <div class="ap-sign-up__check">
        <input id="su-terms" name="terms" type="checkbox" value="yes" required />
        <label for="su-terms">I agree to the <a class="ap-sign-up__link" href="#su-terms-of-use">terms of use</a> and the <a class="ap-sign-up__link" href="#su-privacy">privacy policy</a></label>
      </div>
    </div>
  </div>

  <p class="ap-sign-up__vh" role="status" data-ap-say></p>

  <div class="ap-sign-up__foot">
    <p class="ap-sign-up__status" role="status"><svg class="ap-sign-up__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="m8 12.5 2.6 2.6L16 9.6"/></svg><span data-ap-done></span></p>
    <button type="submit" class="ap-btn ap-btn--primary">Create account</button>
  </div>
</form>

WCAG 2.2

What it meets

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

  • 1.3.5 Identify Input Purpose Level AA

    Name, email and the new password carry the autocomplete tokens name, email and new-password, so browsers fill them in and password managers offer a strong password.

  • 1.4.1 Use of Color Level A

    A met requirement swaps an empty ring for a tick and says Done in text; nothing depends on the green alone.

  • 3.3.1 Error Identification Level A

    Each field with a problem is marked aria-invalid, gets its message in words above it, and is listed in the summary.

  • 3.3.3 Error Suggestion Level AA

    Messages say what to do, like Use at least 12 characters, rather than Invalid password.

  • 3.3.7 Redundant Entry Level A

    Nothing is asked twice: there is no confirm-password field. Show password lets people check what they typed instead of typing it again.

  • 3.3.8 Accessible Authentication (Minimum) Level AA

    The password field accepts paste and is marked new-password, so a password manager can create and save it, and signing in later needs no memory test.

  • 4.1.3 Status Messages Level AA

    Requirement changes and the final result are announced from polite live regions, without moving focus from the field.

Usage

When to use it

Use it

  • Creating an account, where the name, email and password will be reused at every sign-in.
  • Any form that sets a new password, like a reset or an invitation.

Use something else

  • Asking for an account before people can do what they came for: let them check out or browse as a guest.
  • Long profiles at sign-up; ask for the extra details later, when they are needed.
  • Signing in, which needs current-password and no checklist: use the sign-in pattern.

Common failures

How it usually goes wrong

  • A confirm-password field

    Typing a hidden password twice doubles the work and the chance of a slip, and it is no help to people using a password manager. Show password lets them check it once.

  • Blocking paste in the password field

    Stopping paste breaks password managers and forces people to transcribe a long string. Here paste and autofill always work.

  • A checklist that speaks on every key press

    A live region that rereads every rule after each character drowns out the typing. This one waits for a pause and says only what changed.

  • Rules shown only in red and green

    People who cannot tell the two apart see a list that never changes. Each rule here also swaps its ring for a tick and says Done or Not yet in text.

  • Rules revealed only after a failed attempt

    Telling people the password needs 12 characters only after they submit seven wastes a try. The rules are visible before typing starts.

  • Errors that are only listed at the top

    A summary alone leaves people hunting for the field. Each message here also sits above its field, and each summary item links to it.

Notes

Building it

  • WCAG 3.3.7 allows a confirm field for a new password under its security exception, but it is not needed: Show password, paste and password managers catch typing slips better than retyping does.
  • The checklist is the password's description (aria-describedby), so the rules are read when the field is reached. A separate polite live region speaks a requirement only when it changes, about a second after typing stops.
  • Length beats complexity: the rules ask for 12 characters and reject common or personal passwords, instead of demanding a symbol that people put at the end of a word they can guess.
  • If the password is showing when the form is sent, the script hides it again first, so it is not left on screen and password managers see a password field.
  • Sign in and the policy links point to #… anchors in this demo; in your form they are links to those pages. The server checks everything again.

Sources: Understanding SC 3.3.7: Redundant Entry · Understanding SC 3.3.8: Accessible Authentication (Minimum) · HTML: autofill field names

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