Password Input
Password field with a show/hide toggle and an optional strength meter — sizes and states, in React (@bpdm/ui) and Angular (@bpdm/ng).
The Password Input is a password field with a built-in show/hide toggle and an optional
strength meter (a segmented bar plus a label — Weak / Fair / Good / Strong). It renders a real
type="password" input, so browser and third-party password managers work as usual. It runs
controlled (value + onValueChange) or uncontrolled (defaultValue). In React it's
<PasswordInput>; in Angular it's <bpdm-password-input>.
Usage
Drop it in with a placeholder. The eye button toggles visibility; the meter appears once there's input.
import { PasswordInput } from '@bpdm/ui/password-input';
export function SignUp() {
return <PasswordInput placeholder="Password" />;
}For a controlled field, pass value and onValueChange:
import { useState } from 'react';
import { PasswordInput } from '@bpdm/ui/password-input';
export function Controlled() {
const [value, setValue] = useState('');
return <PasswordInput value={value} onValueChange={setValue} placeholder="Password" />;
}import { Component } from '@angular/core';
import { BpdmPasswordInput } from '@bpdm/ng';
@Component({
selector: 'app-sign-up',
standalone: true,
imports: [BpdmPasswordInput],
template: `<bpdm-password-input placeholder="Password" />`,
})
export class SignUpComponent {}For a controlled field, use two-way binding on value:
import { Component } from '@angular/core';
import { BpdmPasswordInput } from '@bpdm/ng';
@Component({
selector: 'app-controlled',
standalone: true,
imports: [BpdmPasswordInput],
template: `<bpdm-password-input [(value)]="value" placeholder="Password" />`,
})
export class ControlledComponent {
value = '';
}Strength meter
As the value changes, the meter fills based on length and character variety (lower-case, upper-case, digits, symbols) and shows a label for the current level. It's live-announced to screen readers.
import { PasswordInput } from '@bpdm/ui/password-input';
export function Strength() {
return <PasswordInput placeholder="Create a password" />;
}import { Component } from '@angular/core';
import { BpdmPasswordInput } from '@bpdm/ng';
@Component({
selector: 'app-strength',
standalone: true,
imports: [BpdmPasswordInput],
template: `<bpdm-password-input placeholder="Create a password" />`,
})
export class StrengthComponent {}Levels & labels
levels sets the number of segments; labels overrides the per-level text (its length should match
levels). Sensible defaults are provided for 3, 4, and 5 levels. For a custom rule, pass a strength
scorer that returns a number from 0 to levels.
Strong
High
import { PasswordInput } from '@bpdm/ui/password-input';
export function Levels() {
return (
<div className="flex w-72 flex-col gap-6">
<PasswordInput levels={3} placeholder="3 levels" />
<PasswordInput levels={5} placeholder="5 levels" />
<PasswordInput levels={3} labels={['Low', 'Mid', 'High']} placeholder="custom labels" />
</div>
);
}import { Component } from '@angular/core';
import { BpdmPasswordInput } from '@bpdm/ng';
@Component({
selector: 'app-levels',
standalone: true,
imports: [BpdmPasswordInput],
template: `
<div class="flex w-72 flex-col gap-6">
<bpdm-password-input [levels]="3" placeholder="3 levels" />
<bpdm-password-input [levels]="5" placeholder="5 levels" />
<bpdm-password-input [levels]="3" [labels]="['Low', 'Mid', 'High']" placeholder="custom labels" />
</div>
`,
})
export class LevelsComponent {}Hiding the meter
The meter suits sign-up and change-password flows. On a sign-in form, where strength guidance is
noise, turn it off with feedback={false}.
import { PasswordInput } from '@bpdm/ui/password-input';
export function SignIn() {
return <PasswordInput feedback={false} placeholder="Password" />;
}import { Component } from '@angular/core';
import { BpdmPasswordInput } from '@bpdm/ng';
@Component({
selector: 'app-sign-in',
standalone: true,
imports: [BpdmPasswordInput],
template: `<bpdm-password-input [feedback]="false" placeholder="Password" />`,
})
export class SignInComponent {}Sizes
Three heights — sm, md (default), and lg — to line up with the other fields in a form.
import { PasswordInput } from '@bpdm/ui/password-input';
export function Sizes() {
return (
<div className="flex w-72 flex-col gap-3">
{(['sm', 'md', 'lg'] as const).map((s) => (
<PasswordInput key={s} size={s} feedback={false} placeholder={`Size ${s}`} />
))}
</div>
);
}import { Component } from '@angular/core';
import { BpdmPasswordInput } from '@bpdm/ng';
@Component({
selector: 'app-sizes',
standalone: true,
imports: [BpdmPasswordInput],
template: `
<div class="flex w-72 flex-col gap-3">
@for (s of sizes; track s) {
<bpdm-password-input [size]="s" [feedback]="false" [placeholder]="'Size ' + s" />
}
</div>
`,
})
export class SizesComponent {
sizes = ['sm', 'md', 'lg'] as const;
}States
Disable the field, or mark it invalid with aria-invalid and pair it with a described error message.
Password is required.
import { PasswordInput } from '@bpdm/ui/password-input';
export function States() {
return (
<div className="flex w-72 flex-col gap-4">
<PasswordInput disabled feedback={false} placeholder="Disabled" />
<div className="flex flex-col gap-1.5">
<PasswordInput aria-invalid aria-describedby="pw-err" feedback={false} placeholder="Password" />
<p id="pw-err" className="text-sm text-destructive">
Password is required.
</p>
</div>
</div>
);
}import { Component } from '@angular/core';
import { BpdmPasswordInput } from '@bpdm/ng';
@Component({
selector: 'app-states',
standalone: true,
imports: [BpdmPasswordInput],
template: `
<div class="flex w-72 flex-col gap-4">
<bpdm-password-input [disabled]="true" [feedback]="false" placeholder="Disabled" />
<div class="flex flex-col gap-1.5">
<bpdm-password-input aria-invalid aria-describedby="pw-err" [feedback]="false" placeholder="Password" />
<p id="pw-err" class="text-sm text-destructive">Password is required.</p>
</div>
</div>
`,
})
export class StatesComponent {}API
PasswordInput / bpdm-password-input
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled value. React: pair with onValueChange. Angular: two-way [(value)]. |
defaultValue | string | "" | Uncontrolled initial value. |
onValueChange (React) | (value: string) => void | — | Called with the value on every change. |
feedback | boolean | true | Show the strength meter below the field. |
levels | number | 4 | Number of strength segments. |
strength | (value: string) => number | — | Custom scorer returning 0..levels (defaults to a length + variety heuristic). |
labels | string[] | — | Per-level labels (length = levels); defaults provided for 3 / 4 / 5. |
size | sm | md | lg | md | Height + text scale. |
disabled | boolean | false | Disable the field and the toggle. |
name | string | — | Native name for form submission (forwarded to the <input>). |
autoComplete | string | — | Set current-password for sign-in, new-password for sign-up. |
aria-invalid | boolean | false | Invalid state — destructive border, announced by assistive tech. |
aria-label / aria-describedby | string | — | Accessible name / description, forwarded to the <input>. |
messages | { show, hide } | English | Override the reveal-toggle labels (screen-reader text) — see Internationalization. |
id | string | — | Field id (for <label> association). |
className (React) / class (Angular) | string | — | Extra classes on the outer wrapper. |
In React, PasswordInput forwards its ref to the underlying <input> and spreads any other
native <input> prop (required, maxLength, data-*, onBlur, …) onto it, so it drops into
forms and test harnesses unchanged. In Angular, name, aria-label, and aria-describedby are
dedicated inputs on <bpdm-password-input>.
Accessibility
- The show/hide control is a real
<button type="button">with anaria-label(Show password/Hide password) andaria-pressedreflecting the current state — operable by keyboard and announced by screen readers. The eye glyph isaria-hidden. - Revealing switches the field between
type="password"andtype="text"; either way it's a standard input, so browser and third-party password managers work. - The strength bar is decorative (
aria-hidden). Its label isaria-live="polite"— so changes are announced — and linked to the field viaaria-describedby, so the current strength is read as the field's description (anyaria-describedbyyou pass is preserved and merged). - Invalid state is set with
aria-invalid— it colours the border and is announced (never colour-only). Pair it witharia-describedbypointing at your error text. - Give the field a name — associate a
<label>(viahtmlFor/id) or addaria-label.
Internationalization
- The reveal-toggle labels (
Show password/Hide password) are translatable via themessagesprop, and the strength-meter labels vialabels— see Translating labels. Give the field a translated accessible name. - RTL-safe: no physical layout (the toggle sits via flex), so the field mirrors under
dir="rtl".