Password input
A password field is a compound, the way a Base UI component is. PasswordField is the root: it owns whether the value is revealed and renders no element of its own. PasswordInput is the control and PasswordToggle the eye; both read the root, so the control's type and the eye's accessible name always agree.
Which dialect a screen gets is decided by what you compose around them, never by a prop:
- The join screens wrap the parts in a floating field:
PasswordInput render={<FloatingFieldControl />}hands itstypeto the floating control, and the toggle rides in the field'strailingslot. - Login, and anywhere the password is checked rather than chosen, puts the control in an input group with the lock icon leading and the toggle trailing, on a screen where nothing floats.
It deliberately does not render a strength meter. The tucked error card requires FieldError to be the group's adjacent sibling, so anything the field wrapped around itself would wedge between the two and detach the error into a floating pill. Where a password is being chosen, compose PasswordStrength after the error line.
Examples
Login
Signing in, changing an email, confirming a destructive action. Lock icon, the dictionary's placeholder, reveal toggle.
Floating label
The join-flow dialect. appearance="white" is the join screens' white card on the auth shell's grey.
Strength
Compose PasswordStrength where a password is being chosen — signup, reset, change. Five rungs, one per requirement, filled to the score, because a member deciding whether to keep typing needs to see how far they have to go: a single "weak" says the password is bad but not what would fix it. The requirements are the meter — each ticks green the moment it is satisfied.
Order matters: the field, then its FieldError, then the meter — never the meter between the two.
<Field data-invalid={invalid}>
<PasswordField>
<FloatingField id="signup-password" trailing={<PasswordToggle />}>
<PasswordInput
render={<FloatingFieldControl autoComplete="new-password" {...field} />}
/>
<FloatingFieldLabel>Password</FloatingFieldLabel>
</FloatingField>
</PasswordField>
{invalid && <FieldError errors={[error]} />}
<PasswordStrength value={password} />
</Field>Invalid and disabled
The same states in both dialects. aria-invalid on the control paints the halo and turns a floating label destructive; the message belongs in a FieldError, not in the meter — the meter reports strength, never form validity.
Scoring
checkStrength lives beside the component in password-input.utils.ts and ticks off five requirements: the schema's minimum length (PASSWORD_MIN_LENGTH, 8), a number, a lowercase letter, an uppercase letter, a symbol. The verdict is the count: none met is empty, up to two is weak, up to four is medium, all five is strong.
It is a hint, not the gate. The gate is the schema's minimum length and the auth provider's own policy behind it. Import PASSWORD_MIN_LENGTH from the same file when writing the validation schema, so the gate and the meter cannot drift apart.
Notes
- The meter takes the current
value— wire it to the same state orwatchthe form field, so it never depends on how the caller wired the control. - The verdict is
aria-live="polite", so a screen reader hears it change without the value being read back on every keystroke. - The bar is a Base UI
Meter(rolemeter,aria-valuenowis the score, the value text the "n/5 requirements met" line); each requirement carries a visually hidden "met" / "not met". - State lives on the DOM:
data-visibleon the control and the toggle while the value is revealed;data-strength="empty | weak | medium | strong"on the meter,data-scoreon its bar,data-state="filled | empty"on each rung and"met | unmet"on each requirement. - Set
autoComplete="new-password"when choosing and"current-password"when signing in, so password managers offer the right thing.
API
PasswordField:
| Prop | Type | Description |
|---|---|---|
| defaultVisible | boolean | Start revealed. Rarely right: a password is hidden until asked. |
PasswordInput — InputGroupInput's props, plus:
| Prop | Type | Description |
|---|---|---|
| render | ReactElement | The control to render instead of an InputGroupInput — a FloatingFieldControl inside a FloatingField. It receives the type the root decides. |
PasswordToggle takes InputGroupButton's props; its aria-label, onClick and icon are the root's.
PasswordStrength:
| Prop | Type | Description |
|---|---|---|
| value | string | The password being judged. Empty renders a silent meter. |
| className | string | Extra classes on the meter's wrapper. |