Password input
StableA password field with a Show / Hide button and a caps-lock warning, where paste and password managers always work.
The button’s words and its name change together (“Show password” / “Hide password”), and a hidden live message confirms each change. Submitting the form turns the field back into a password field, so browsers never save it as plain text. Takes every <input> attribute except type and size; the ref reaches the input.
Sign in
import { useState, type FormEvent } from 'react'import { PasswordInput } from '@rchursin/product/forms'import { Button, Field, TextInput } from '@rchursin/product/ui'
/** * Show / Hide reveals what was typed; submitting hides it again, so the browser never saves * the password as plain text. Turn caps lock on while typing to see the warning. */export default function PasswordSignIn() { const [email, setEmail] = useState('alex@example.com') const [password, setPassword] = useState('') const [sent, setSent] = useState(false)
function submit(e: FormEvent) { e.preventDefault() // a sample: nothing leaves the page setSent(true) }
return ( <form noValidate onSubmit={submit} className="flex w-full max-w-sm flex-col gap-4">Usage
import { PasswordInput, type PasswordInputProps } from '@rchursin/product/forms'<Field label="Password"> {(p) => ( <PasswordInput {...p} autoComplete="current-password" value={password} onChange={(e) => setPassword(e.target.value)} /> )}</Field>Examples
New password
The large size with autoComplete="new-password". Press Save with a short password: the error says what to fix and focus returns to the field.
import { useRef, useState, type FormEvent } from 'react'import { PASSWORD_MIN, PasswordInput, checkNewPassword } from '@rchursin/product/forms'import { Button, Field } from '@rchursin/product/ui'
/** * A new password in the large size. The rule is stated up front; a press that fails says what * to fix and moves focus back to the field. */export default function PasswordNew() { const input = useRef<HTMLInputElement>(null) const [password, setPassword] = useState('') const [error, setError] = useState<string>() const [saved, setSaved] = useState(false)
function submit(e: FormEvent) { e.preventDefault() const problem = checkNewPassword(password) setError(problem) setSaved(!problem) if (problem) input.current?.focus()API
Password input props
| Prop | Type | Default | Description |
|---|---|---|---|
autoCompleterequired | 'current-password' | 'new-password' | – | current-password to sign in; new-password to sign up, reset or change, so password managers offer a strong one. |
size | 'md' | 'lg' | 'md' | Height 32 or 40 pixels. The Show / Hide button grows with it. |
describedBy | string | – | Extra ids for aria-describedby, e.g. a strength meter. Joined with the hint and error ids from Field. |
className | string | – | Classes for the wrapper (the field and the caps-lock note), not the input. |
Accessibility
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves from the field to the Show / Hide button, then on. |
| EnterSpace | On the button: shows or hides the password. Focus stays on the button. |
| CtrlV | Pastes. Nothing blocks paste or password managers. |
- Show / Hide is a real button with aria-controls pointing at the field. Its name changes with its words (“Show password” / “Hide password”) instead of using aria-pressed, so the state is never said twice.
- A visually hidden polite live region says “Your password is visible” or “Your password is hidden” after each press.
- With caps lock on, “Caps lock is on.” appears under the field, joins its aria-describedby and is announced once, through the AnnouncerProvider at the root of the app. Safari and Firefox on macOS draw their own indicator, so the field stays quiet there.
- Spell check, autocorrect and autocapitalize are off, so the browser never changes or sends off what was typed.
Guidelines
Do
- Set autoComplete on every password field: current-password to sign in, new-password everywhere else.
- State the rules in the hint before people type: “Use 15 characters or more”.
- Keep the field inside a <form>, so submitting hides the password again.
Don’t
- Don’t block paste or add a “confirm password” field; Show does that job.
- Don’t add composition rules (a digit, a symbol); length and a blocklist are what help.
In use
Related
- Password strengthAn advisory meter that guesses like an attacker, plus the blocklist check. Four grey segments and a word; it never blocks.
- Sign-in flowThe whole front door in one component: email first, password or passkey, GitHub or Google, sign-up with a code, and recovery.
- Text inputA one-line field in the inset lux-input finish, in two sizes, with an optional icon inside and a slot on the right.
- FieldLabel above, hint under it, error in words between the hint and the control, with the ids and aria wiring done for you.
Source
The component’s own files, exactly as they run on this site.
/** * Password field with a Show/Hide button and a caps-lock warning (GOV.UK and NHS pattern). * * <Field label="Password" error={err}> * {(p) => <PasswordInput {...p} autoComplete="current-password" value={pw} onChange={…} />} * </Field> * * - The button's visible text changes (Show / Hide) and so does its name ("Show password" / * "Hide password"). It uses a changing label, not aria-pressed: never both. * - A hidden live message says "Your password is visible" / "Your password is hidden". * - When the form submits, the field goes back to type="password" so browsers never save the * password as plain text. * - Paste and password managers always work (WCAG 3.3.8, NIST SP 800-63B-4). * - Caps lock: "Caps lock is on." under the field, announced once. Best effort: skipped where * the browser shows its own indicator (Safari and Firefox on macOS). */import { forwardRef, useEffect, useId,