Skip to main contentSkip to docs navigation

Password Strength

Chassis PasswordStrength renders a segmented meter or progress bar giving users real-time feedback on password complexity as they type.

Overview

PasswordStrength scores a password against a set of weighted criteria (length, character variety, special characters) and renders the result as either four discrete segments or a single growing bar, both styled by chassis-css's .strength/.strength-bar. It's display-only — pass it the current password value from a controlled <input>, and it computes and renders the rest.

JSX
import { useState } from 'react'
import { TextInput, FormField, PasswordStrength } from '@chassis-ui/react'

export const Example = () => {
  const [password, setPassword] = useState('')

  return (
    <FormField label="Password" ids={{ input: 'password1', label: 'password1Label' }}>
      <TextInput
        aria-labelledby="password1Label"
        autoComplete="new-password"
        id="password1"
        onChange={setPassword}
        placeholder="Enter password"
        type="password"
        value={password}
      />
      <PasswordStrength value={password} />
    </FormField>
  )
}

Progress bar variant

Pass variant="bar" for a single bar that grows with password strength instead of four segments.

JSX
import { useState } from 'react'
import { TextInput, FormField, PasswordStrength } from '@chassis-ui/react'

export const Example = () => {
  const [password, setPassword] = useState('')

  return (
    <FormField label="Password" ids={{ input: 'password2', label: 'password2Label' }}>
      <TextInput
        aria-labelledby="password2Label"
        autoComplete="new-password"
        id="password2"
        onChange={setPassword}
        placeholder="Enter password"
        type="password"
        value={password}
      />
      <PasswordStrength value={password} variant="bar" />
    </FormField>
  )
}

As a form field

Associate the meter's text feedback with the password field via aria-describedby, alongside a FormHelp describing the requirements.

Use 8 or more characters with a mix of letters, numbers & symbols.
JSX
import { useState } from 'react'
import { TextInput, FormField, PasswordStrength } from '@chassis-ui/react'

export const Example = () => {
  const [password, setPassword] = useState('')

  return (
    <FormField
      label="Password"
      help="Use 8 or more characters with a mix of letters, numbers & symbols."
      ids={{ help: 'password3Help', input: 'password3', label: 'password3Label' }}
    >
      <TextInput
        aria-describedby="password3Help"
        aria-labelledby="password3Label"
        autoComplete="new-password"
        id="password3"
        onChange={setPassword}
        placeholder="Enter password"
        type="password"
        value={password}
      />
      <PasswordStrength value={password} />
    </FormField>
  )
}

Strength levels

Four levels — weak, fair, good, and strong — are driven by the default scoring criteria below.

Weak
Weak
Fair
Fair
Good
Good
Strong
Strong
JSX
import { PasswordStrength } from '@chassis-ui/react'

const samples: { label: string; password: string }[] = [
  { label: 'Weak', password: 'abc' },
  { label: 'Fair', password: 'abcdefgh1' },
  { label: 'Good', password: 'Abcdefgh1234' },
  { label: 'Strong', password: 'Sup3r!Secret!Passphrase99' }
]

export const Example = () => {
  return (
    <div className="vstack gap-md">
      {samples.map(({ label, password }) => (
        <div key={label}>
          <div className="fg-subtle">{label}</div>
          <PasswordStrength aria-label={`${label} example`} value={password} />
        </div>
      ))}
    </div>
  )
}

Custom weights and thresholds

Pass weights to change or disable individual scoring criteria (set a criterion to 0 to drop it entirely), thresholds to move the [weak, fair, good] score boundaries, and messages to customize the feedback text.

JSX
import { useState } from 'react'
import { TextInput, FormField, PasswordStrength } from '@chassis-ui/react'

export const Example = () => {
  const [password, setPassword] = useState('')

  return (
    <FormField label="Password" ids={{ input: 'password4', label: 'password4Label' }}>
      <TextInput
        aria-labelledby="password4Label"
        autoComplete="new-password"
        id="password4"
        onChange={setPassword}
        placeholder="No uppercase required, symbols count double"
        type="password"
        value={password}
      />
      <PasswordStrength
        messages={{ weak: 'Too weak', strong: 'Excellent!' }}
        thresholds={[3, 5, 7]}
        value={password}
        weights={{ special: 2, uppercase: 0 }}
      />
    </FormField>
  )
}

Strength criteria

The default scoring algorithm evaluates passwords based on:

CriteriaPoints
Meets minimum length (minLength, 8 by default)+1
4+ characters over minLength+1
Contains lowercase letters+1
Contains uppercase letters+1
Contains numbers+1
Contains special characters+1
Multiple special characters+1
16+ characters+1
LevelPoints (default thresholds)
Weak1–2
Fair3–4
Good5–6
Strong7–8

Pass scorer to replace this algorithm entirely with a custom (password) => number function.

Accessibility

The meter carries role="meter" (via React Aria's useMeter) with aria-valuenow/aria-valuemin/aria-valuemax kept in sync with the score, and an accessible name from aria-label (defaults to "Password strength"). The .strength-text feedback element is an aria-live="polite" region, so screen reader users hear the level change as they type — the meter is decorative feedback and should always be paired with visible password requirements text, not relied on alone.

Scope

Built on React Aria's useMeter for the accessible display — there's no dedicated password-strength hook, so the scoring algorithm itself is ported directly from chassis-css's vanilla strength.js (weights/thresholds/scorer options included) rather than coming from the library.

Unlike the vanilla component, PasswordStrength doesn't look up a sibling password <input> itself or expose imperative getStrength()/evaluate() methods or a strengthChange DOM event — pass the current password as value and read the result declaratively via onStrengthChange instead.

API

PasswordStrength

PropTypeDefault
aria-label?stringPassword strength
An accessible label for the meter. Defaults to "Password strength".
className?string—
A string of all className you want applied to the base component.
id?string—
id forwarded to the meter element.
maxScore?number—
The maximum possible score, used to compute the meter's aria-valuemax. Defaults to the sum of weights' values — set this explicitly when pairing a custom scorer whose scale doesn't match the built-in criteria weights, otherwise aria-valuenow/aria-valuemax get silently clamped to the built-in max.
messages?Partial<Record<StrengthLevel, string>>—
Feedback message shown for each strength level, when showText is true.
minLength?number8
Minimum password length required to earn the first strength point. Defaults to 8.
onStrengthChange?((result: { score: number; strength: StrengthLevel | null; }) => void)—
Callback fired whenever the strength level changes, including once on mount with the initial value's strength.
scorer?((password: string) => number)—
Custom scoring function overriding the built-in criteria. Receives the password and returns a numeric score.
showText?booleantrue
Renders a .strength-text element below the meter with the current level's message. Defaults to true.
thresholds?[number, number, number][2, 4, 6]
Score boundaries [weak, fair, good] — scores above the last value are "strong". Defaults to [2, 4, 6].
valuestring—
The current password value to evaluate.
variant?"bar" | "segmented"segmented
Render as four discrete segments, or a single growing bar. Defaults to "segmented".
weights?Partial<StrengthWeights>—
Point values for each scoring criterion. Set a criterion to 0 to disable it. Merged with the built-in defaults.