Accessibility pattern · Forms and flows

Sign in

Nothing here asks people to remember or transcribe anything the browser cannot help with: autocomplete lets a password manager fill both fields, paste works, and Show password lets people check what they typed. A failed attempt gets one honest message, Email or password is wrong, with a reset link, in a summary that takes focus, and a passkey or an emailed link offers a way in with no password at all.

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
Tab or ShiftTabMoves through email or username, password, Show password, the reset link, Keep me signed in, Sign in and the two other ways in.
Enter or SpaceOn Show password, shows the password as text; pressed again, hides it. Focus stays on the button.
EnterIn a field, signs in. Problems, or a wrong email and password, are shown in a summary that takes focus.
SpaceOn Keep me signed in on this device, ticks or unticks it.

Screen readers

What it announces

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

WhenExpected announcement
Focus reaches the password fieldPassword, secure edit text, required
Show password is pressedYour password is shown.
Sign in is pressed with the password emptyThere is a problem, group. Enter your password
Sign in is pressed with a wrong passwordEmail or password is wrong, group. Check both and try again. Passwords are case-sensitive. Reset your password
Email me a sign-in link is pressed with an email addressSign-in link sent to your email. It works once, for 15 minutes. This demo sends nothing.
Sign in with a passkey is pressedSigned in with your passkey. A real site asks your device for a fingerprint, face or screen lock first.

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-in" data-ap-sign-in novalidate aria-labelledby="si-form-name">
  <div class="ap-sign-in__top">
    <h3 class="ap-sign-in__name" id="si-form-name">Sign in</h3>
    <p class="ap-sign-in__sub">Welcome back to Studio.</p>
  </div>

  <div class="ap-sign-in__summary" id="si-summary" role="group" aria-labelledby="si-summary-name" aria-describedby="si-summary-body" tabindex="-1" hidden>
    <h4 class="ap-sign-in__summary-name" id="si-summary-name"><svg class="ap-sign-in__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 data-ap-summary-name>There is a problem</span></h4>
    <div class="ap-sign-in__summary-body" id="si-summary-body">
      <ul class="ap-sign-in__summary-list" data-ap-fields></ul>
      <div class="ap-sign-in__summary-auth" data-ap-auth hidden>
        <p>Check both and try again. Passwords are case-sensitive.</p>
        <p><a href="#sign-in-reset">Reset your password</a></p>
      </div>
    </div>
  </div>

  <div class="ap-sign-in__body">
    <div class="ap-field ap-sign-in__field">
      <label class="ap-label" for="si-id">Email or username</label>
      <input class="ap-input" id="si-id" name="username" type="text" inputmode="email" autocomplete="username" autocapitalize="none" spellcheck="false" required />
      <p class="ap-error ap-sign-in__error" id="si-id-error" hidden><svg class="ap-sign-in__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-in__vh">Error:</span> <span data-ap-text></span></p>
    </div>

    <div class="ap-field ap-sign-in__field">
      <label class="ap-label" for="si-password">Password</label>
      <div class="ap-sign-in__pw">
        <input class="ap-input" id="si-password" name="password" type="password" autocomplete="current-password" required />
        <button type="button" class="ap-sign-in__show" data-ap-show aria-controls="si-password"><span data-ap-show-word>Show</span><span class="ap-sign-in__vh"> password</span></button>
      </div>
      <p class="ap-error ap-sign-in__error" id="si-password-error" hidden><svg class="ap-sign-in__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-in__vh">Error:</span> <span data-ap-text></span></p>
      <a class="ap-sign-in__link" href="#sign-in-reset">Forgot your password?</a>
    </div>

    <div class="ap-sign-in__check">
      <input type="checkbox" id="si-remember" name="remember" aria-describedby="si-remember-hint" />
      <div>
        <label for="si-remember">Keep me signed in on this device</label>
        <p class="ap-hint" id="si-remember-hint">Leave this unticked on a shared computer.</p>
      </div>
    </div>

    <button type="submit" class="ap-btn ap-btn--primary ap-sign-in__go">Sign in</button>

    <p class="ap-sign-in__or"><span>or</span></p>

    <div class="ap-sign-in__alt">
      <button type="button" class="ap-btn" data-ap-passkey>
        <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="8" cy="9" r="4"/><path d="M2.5 20a5.5 5.5 0 0 1 9.6-3.7"/><path d="M17 11.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z"/><path d="M17 16.5V21l1.5-1"/></svg>
        <span data-ap-passkey-word>Sign in with a passkey</span>
      </button>
      <button type="button" class="ap-btn" data-ap-magic>
        <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3.5 6.5 8.5 6.5 8.5-6.5"/></svg>
        Email me a sign-in link
      </button>
    </div>

    <p class="ap-sign-in__status" role="status"><svg class="ap-sign-in__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>
    <p class="ap-sign-in__vh" role="status" data-ap-live></p>
  </div>

  <div class="ap-sign-in__foot">
    <p>New to Studio? <a class="ap-sign-in__link" href="#sign-in-create">Create an account</a></p>
    <p class="ap-sign-in__demo">Demo: [email protected] with monsoon-chai-42 signs in; anything else shows the error. Nothing is sent.</p>
  </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

    The fields carry autocomplete="username" and autocomplete="current-password", so password managers and browsers fill them reliably.

  • 2.4.3 Focus Order Level A

    After a failed attempt, focus moves to the summary at the top, so the message is read first and Tab goes on to the fields.

  • 3.3.1 Error Identification Level A

    An empty field is marked aria-invalid with its own message; a wrong pair of details is described in words in the summary.

  • 3.3.3 Error Suggestion Level AA

    The failure message says what to check and offers the next step, a password reset, as a link.

  • 3.3.8 Accessible Authentication (Minimum) Level AA

    No step depends on remembering or transcribing: paste and password managers work, Show password helps check typing, there is no puzzle, and a passkey or emailed link needs no password.

  • 4.1.2 Name, Role, Value Level A

    Show password is a real button, named Show password or Hide password by its words, that controls the field.

  • 4.1.3 Status Messages Level AA

    Showing the password, a sent link and a passkey sign-in are announced from status regions, without moving focus.

Usage

When to use it

Use it

  • Any sign-in that takes a password, on its own page or in a dialog.
  • Accounts where some people will prefer a passkey or an emailed link over a password.

Use something else

  • A puzzle CAPTCHA to stop bots: use rate limiting and checks on the server that people never see.
  • Splitting sign-in over two screens with no reason: password managers fill one form most reliably.
  • Telling people which of the two was wrong: it tells attackers which accounts exist.

Common failures

How it usually goes wrong

  • Paste blocked in the password field

    Blocking paste stops password managers and forces people to type long passwords from memory, which fails WCAG 3.3.8. Paste works here.

  • Autocomplete turned off

    autocomplete="off" on a password field breaks password managers for many people. username and current-password tell them exactly what to fill.

  • A puzzle to prove you are human

    Picture and text puzzles are a cognitive function test with no alternative, and many people cannot pass them. Stop bots on the server instead.

  • "Invalid credentials" and nothing else

    Jargon with no way forward leaves people stuck. The message here is plain and comes with a link to reset the password.

  • A show password icon with no name

    An eye icon button with no accessible name is announced as just "button". This one says Show password or Hide password.

  • Errors that appear where nobody is looking

    A failed sign-in message at the bottom of a long page goes unnoticed. The summary is at the top of the form and takes focus.

Notes

Building it

  • Show password switches the field's type between password and text and changes its own words; a status message says which, since a type change is not announced.
  • The password is hidden again before the form is sent, so browsers offer to save it as a password and never as plain text.
  • The error does not say whether the email or the password was wrong: that would tell an attacker which accounts exist. Keep it generic, and make the next step easy.
  • A real passkey button calls navigator.credentials.get() and the device asks for a fingerprint, face or screen lock; this demo only stands in for that prompt.
  • The links stand for the reset and sign-up pages here (#…); in your own form, point them at the real pages.

Sources: Understanding SC 3.3.8: Accessible Authentication (Minimum) · HTML: autofill field names · 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