@passcore/react

React

@passcore/react brings a controlled <PasswordStrengthMeter> component and the usePasswordStrength() hook, for React 18 and later.

pnpm add @passcore/react
Read the README

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.

TSX
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.

TSX
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.

TSX
<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.

TSX
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.

TSX
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.

TSX
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.

TSX
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.

TSX
<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
Custom properties
Presets
CSS
.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.