@passcore/react
React
@passcore/react brings a controlled <PasswordStrengthMeter> component and the usePasswordStrength() hook, for React 18 and later.
pnpm add @passcore/react
Every meter on this page speaks your language. The playground gives each one the texts of the language you pick at the top, and its accessible name. To keep the snippets short, only the translation demos show how; the show and hide button in the fields belongs to the playground, not to the package.
Default
The component is controlled: it takes the password as a prop and never touches your input. It has no hidden state, so the meter is always shown; id links it to the field.
import { PasswordStrengthMeter } from '@passcore/react'
import '@passcore/react/styles.css'
const [password, setPassword] = useState('')
<input
id="password"
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
aria-describedby="password-strength"
/>
<PasswordStrengthMeter id="password-strength" password={password} />
Requirements checklist
The classic signup form, built with your own markup: usePasswordStrength() returns the result, and result.rules lists every enabled rule, in order, with its id, passed and params. The component draws the bar next to it. Every item stays grey until something is typed: an empty password is “not common” too, and saying so in green would mislead.
import { PasswordStrengthMeter, usePasswordStrength } from '@passcore/react'
const rules = { minLength: 8, lowercase: 1, uppercase: 1, numbers: 1, symbols: 1 }
const { result } = usePasswordStrength(password, { userInputs: [username], rules })
const typed = password !== ''
const met = result.rules.filter((rule) => rule.passed).length
<PasswordStrengthMeter password={password} userInputs={[username]} rules={rules} />
<ul>
{result.rules.map((rule) => (
<li key={rule.id} data-state={!typed ? 'idle' : rule.passed ? 'met' : 'unmet'}>
{t(`requirements.rule.${rule.id}`, { count: rule.params.min })}
</li>
))}
</ul>
<p aria-live="polite">
{typed ? `${met} of ${result.rules.length} requirements met` : `${result.rules.length} requirements to meet`}
</p>
With the percent
showPercent adds the number next to the bar.
<PasswordStrengthMeter
id="password-strength"
password={password}
showPercent
/>
Linked to a username
With userInputs the password can’t contain the username. Pass the current values, straight from state.
const [username, setUsername] = useState('')
<input
type="text"
autoComplete="username"
value={username}
onChange={(event) => setUsername(event.target.value)}
/>
<PasswordStrengthMeter
id="password-strength"
password={password}
userInputs={[username]}
showPercent
/>
Bundled translations
The bundled files go in translations, locale picks the plural forms and label names the meter for screen readers. Switch the language at the top: new props, new texts.
import en from '@passcore/react/locales/en.json'
import es from '@passcore/react/locales/es.json'
import ca from '@passcore/react/locales/ca.json'
const locales = { en, es, ca }
<PasswordStrengthMeter
password={password}
translations={locales[lang]}
locale={lang}
label={labels[lang]}
showPercent
/>
Translations with i18next
translate takes the t of an i18next instance, here the playground’s own. The function never changes, so locale tells the component when to translate again.
import i18next from 'i18next'
import es from '@passcore/react/locales/es.json'
import ca from '@passcore/react/locales/ca.json'
i18next.addResourceBundle('es', 'passcore', es)
i18next.addResourceBundle('ca', 'passcore', ca)
const translate = (key, params) => i18next.t(key, { ns: 'passcore', ...params })
// the same function in every language: locale tells the meter to translate again
<PasswordStrengthMeter
password={password}
translate={translate}
locale={lang}
label={labels[lang]}
showPercent
/>
Callbacks and events
onScore gets the percent whenever the result changes (never on mount). Here it drives the score and the Send button.
const [percent, setPercent] = useState(0)
<PasswordStrengthMeter password={password} onScore={setPercent} />
<output>{percent}%</output>
<button type="submit" disabled={percent <= 75}>Send</button>
Input group
The meter is just another element: put it after the input group and it spans the whole group.
<div className="form-group">
<label htmlFor="password">Password</label>
<div className="input-group">
<span className="input-group__addon">…</span>
<input id="password" type="password" value={password} onChange={…} />
</div>
<PasswordStrengthMeter password={password} />
</div>
Theming
Colours, track and height are CSS custom properties: --pass-color-very-weak to --pass-color-strong, --pass-track and --pass-height. Play with them here; the CSS below follows along.
- Very weak
- Weak
- Fair
- Good
- Strong
.signup {
--pass-height: 6px;
--pass-track: #e4e2db;
--pass-color-very-weak: #d92d20;
--pass-color-weak: #e06a0f;
--pass-color-fair: #c99400;
--pass-color-good: #5e9e0f;
--pass-color-strong: #16884a;
}
The controls only change this preview, so the other demos keep their look. Set the properties on :root to theme a whole site.