When to use
Any sign-in, login, sign-up or account recovery step, and any bot check on it: "type the letters in the image", a math question, a slider puzzle, "pick all the traffic lights", a code the user must copy by hand, a security question. Also when the request itself asks for such a check.
The rule agents break: building the puzzle that was asked for
"Keep bots out with five distorted letters" is easy to build, and the result looks right. It locks people out. Transcribing distorted text, solving a puzzle, recalling a code or doing sums is a cognitive function test, and no step of signing in may depend on one alone. Someone with dyslexia, a memory or learning disability, low vision or a screen reader cannot pass it.
A step may use such a test only when it also offers at least one of:
Another way in that needs no test, such as an emailed sign-in link or a passkey.
Help that does the work: the password field accepts paste and the password manager's autofill, and a code field accepts paste.
Object recognition or personal content the user gave earlier, such as "pick the photo you uploaded". This is the weakest choice; avoid it.
When asked for a letters puzzle, keep bots out without it: a hidden honeypot field, rate limits on the server, or a service's invisible check. If the user insists on a visible challenge, add an equal way past it ("Email me a sign-in link"), and say in your reply why.
The sign-in form
<form id="sign-in" novalidate>
<label for="email">Email</label>
<input
id="email"
name="email"
type="email"
autocomplete="username"
required
/>
<label for="password">Password</label>
<input
id="password"
name="password"
type="password"
autocomplete="current-password"
required
/>
<div class="trap" aria-hidden="true">
<label for="website">Leave this empty</label>
<input id="website" name="website" tabindex="-1" autocomplete="off" />
</div>
<button type="submit">Sign in</button>
<button type="button" id="email-link">Email me a sign-in link</button>
</form>.trap {
position: absolute;
left: -10000px;
}form.addEventListener("submit", (event) => {
event.preventDefault();
if (form.website.value !== "") return;
signIn(form.email.value, form.password.value);
});autocomplete="username"on the email andcurrent-passwordon the password let password managers fill both. Usenew-passwordon sign-up.The honeypot is off screen, out of the Tab order and hidden from screen readers; bots fill it, people do not.
DO
Allow paste in every sign-in field, including one-time code fields (
autocomplete="one-time-code").Offer "Show password" as a toggle button that switches
typebetweenpasswordandtext.Let a code field take the whole code in one input, not one character per box.
DO NOT
Do not block paste with
onpaste="return false"orpreventDefault()on paste.Do not ask for the password twice at sign-in, or for characters 3, 5 and 8 of it.
Do not set
autocomplete="off"on the email or password.Do not make a typed puzzle, sums or a slider the only way in.
React and frameworks
Use autoComplete="username" and autoComplete="current-password". Keep the honeypot uncontrolled or controlled like any field, and check it in onSubmit. Do not attach onPaste handlers that call preventDefault(). If a CAPTCHA library is required, pick one with an invisible or no-interaction mode.
What to verify
Sign in with only email and password, filled by the password manager or pasted: it works.
No step asks the user to type what an image shows, solve sums or remember a code, unless another way past it is on the same page.