ui-055

Password Strength Meter

£5

A password strength meter: a four-segment bar sweeps in and drains, rule ticks draw as each is met, with a confirm field match tick.

intermediate
6 more details
micro-interactionform-animationform-validationsvg-line-drawstaggerkeyboard-navigation
Password Strength Meter - UI component preview

This element reads better at your own screen size than in the frame below:

About this component

A sign-up password field that shows its working. As the visitor types, a four-segment meter fills with a short left-to-right sweep inside each segment, and drains back in reverse when a character comes out. The strength label beside it rolls up to Weak, Fair, Good or Strong and rolls down again on the way back, and each rule in the checklist draws a tick in its circle the moment it is met and un-draws it when it is lost. A confirm field below stays quiet while it is still a prefix of the password, then shows a mismatch or draws a match tick, and the Create account button only enables once the password is Strong and confirmed.

Read the full effect overview

The scoring is real and deterministic: length and character variety as an estimate in bits, a built-in list of the most common passwords (with letter swaps and bolted-on digits undone), and penalties for repeats and runs such as abcd, 4321 or qwer. It is also pluggable, so a buyer can hand the score to zxcvbn with one attribute. The field is a native password input with an eye toggle that keeps the caret where it was, the strength is announced politely once typing pauses, and each rule states met or not met to screen readers. Ships in light and dark, switched with one data-variant attribute.

What's included

10 items
  • Four-segment meter: each segment sweeps in left to right, and drains in reverse
  • Strength label rolls up or down between Weak, Fair, Good and Strong
  • Rule ticks draw themselves in when met and un-draw when lost
  • Deterministic scoring: bits from length and variety, a common-password list, penalties for repeats and runs
  • Pluggable scorer: name a function in data-scorer (zxcvbn drops straight in)
  • Eye toggle is a button with aria-pressed that keeps focus and the caret position
  • Confirm field waits while it is still a prefix, then shows mismatch or a drawn match tick
  • Create account stays disabled until the password is Strong and confirmed
  • Strength announced through a debounced polite live region, rules expose met or not met in text
  • Light and dark themes on one attribute, status colours muted and used only for strength

Perfect for

4 use cases
  • Sign-up and account creation forms
  • Password reset and change-password screens
  • Invitation acceptance flows that set a first password
  • Admin consoles that enforce a password policy

How it works

3 sections

Sweep in, drain out

Each segment holds a fill scaled on x from its left edge. When the score rises, only the newly earned segments tween to scaleX 1, one after another from the left; when it falls, the segments above the new score tween back to 0 from the right. The colour comes from a data-level attribute on the meter, so the status tokens stay in CSS.

A label that always tells the truth

The visible label is updated immediately, because the input's aria-describedby reads it. An aria-hidden copy of the old word is laid over it and rolls out while the new one rolls in, upward for stronger and downward for weaker. A separate live region speaks the strength and the count of met rules only after typing has paused, so it never chatters per keystroke.

Scoring you can read and replace

The built-in scorer counts length minus predictable characters (the third and later of a repeat or a run) and multiplies by log2 of the character pool: under 30 bits is Weak, under 45 Fair, under 60 Good, otherwise Strong. A common password is Weak regardless, fewer than eight characters is Weak, and Strong needs every rule in the checklist. Any function named in data-scorer replaces the estimate; the checklist caps still apply.

Difficulty Intermediate
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this UI element's demo, 15 September 2026. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Paid UI element

Purchase to unlock the code.

Buying Password Strength Meter opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects.

£5 Standard license, unlimited projects

Browse UI elements

Documentation

What's Included

  • index.html: the demo page and the markup to copy

  • assets/style.css: component styles, plain CSS custom properties for colours

  • assets/script.js: readable, commented source with an onReady guard

  • Each newly earned segment sweeps in from its own left edge, one after another; lost segments drain right to left

  • The strength label rolls up for stronger and down for weaker, and is never mid-swap for a screen reader

  • Rule ticks are drawn with stroke-dashoffset from each path's measured length, and un-drawn when a rule is lost

  • Real, deterministic scoring with a common-password list and penalties for repeats and runs, documented below and replaceable with one attribute

  • The eye toggle keeps the value, the focus and the caret position across the type swap

  • The confirm field says nothing while it is still a prefix of the password, then shows a mismatch or draws a match tick

  • The submit button is disabled until the password is Strong, every rule is met and the confirm field matches

  • Native <input type="password">, aria-pressed, aria-describedby, aria-invalid and a polite live region

Quick Start

1. Add to your <head>:

Code snippet omitted: it ships with the download.

2. Add before the closing </body> tag:

Code snippet omitted: it ships with the download.

3. Copy the <form class="psm" data-psm> block from index.html into your page.

The script looks only at data- hooks inside each [data-psm] root, so several can share a page:

Hook On Required What it is
data-psm the form (or any wrapper) yes One component instance
data-psm-input the password input yes The field that is scored
data-psm-toggle a <button> no The eye toggle; data-psm-slash marks the slash path it draws
data-psm-meter the meter row no Receives data-level="0" to "4"
data-psm-fill four spans no The segment fills; any count works
data-psm-label a span no The strength word; give it the id the input's aria-describedby names
data-psm-live a visually hidden span no The debounced announcement
data-rule="length|case|number|symbol|common" each <li> no A checklist rule; data-rule-tick marks its tick path and data-rule-state its hidden "Met: / Not met: " text
data-psm-confirm the confirm input no Enables the match state; data-psm-match-icon and data-psm-match are its tick and message
data-psm-submit the submit button no Disabled until valid; data-psm-submit-text holds its label
data-min-length the root no Minimum length for the length rule, default 12 (edit the rule's text to match)
data-scorer the root no Name of a global scoring function, see Scoring
data-psm-demo the root no Demo only: a valid submit is confirmed in place instead of sent

Leave out data-psm-demo in production and a valid form submits normally. An invalid submit (Enter in the field) is cancelled and focus goes back to the password.

The demo toolbar and the .stage wrapper in index.html are demo furniture, not part of the component; leave them behind. assets/script.js reads no .stage-* class.

Keyboard & Accessibility

Key What it does
Typing / Backspace Rescores on every input event; the meter, label and ticks follow
Tab Password, eye toggle, confirm password, Create account (skipped while disabled)
Space / Enter on the eye Shows or hides the password; the input keeps its value and caret
Enter in a field Submits when valid; otherwise cancelled with focus returned to the password
  • The password input has aria-describedby="psmStrength psmRules", pointing at the strength word and the checklist, so both are read when it takes focus. The visible word is always the current one: the rolling copy of the old word is aria-hidden.
  • Announcements: a visually hidden aria-live="polite" region speaks "Password strength: Good. 3 of 5 requirements met." only after 900ms without input, and only when that sentence changes, so it never chatters per keystroke. The visible label is not itself live.
  • Rules carry hidden text that switches between "Met: " and "Not met: ", so the state is never colour or icon alone. The tick marks are aria-hidden.
  • The eye toggle is a <button type="button"> labelled "Show password" with aria-pressed for its state. A pointer press is cancelled on pointerdown so focus stays in the input, and the selection is restored after the type swap.
  • The confirm field gets aria-invalid="true" on a mismatch and is described by its message, which is itself a polite live region.
  • The submit button uses the native disabled attribute until valid.
  • prefers-reduced-motion: reduce: the same end states with every duration at zero, read live so a change while the page is open applies at once.
  • Without JavaScript the form is a plain pair of password inputs with required and minlength="12", and the submit button stays enabled.

Scoring

The built-in scorer is PasswordStrengthMeter.score(value), returning 0 (empty) to 4:

  1. Common passwords are Weak. The value is lower-cased, common letter swaps are undone (P@ssw0rd), and trailing digits and symbols are stripped (Password123!); if any of those forms is on the built-in list, the score is 1.
  2. Predictable characters count for nothing. The third and later character of a repeat (aaa) or of a run (abcd, 4321, or qwer along a keyboard row) is removed from the length.
  3. Bits = remaining length x log2(character pool), where the pool adds 26 for lower case, 26 for upper case, 10 for digits and 33 for symbols. Under 30 bits is Weak, under 45 Fair, under 60 Good, and 60 or more Strong.

The component then applies two caps whatever scorer is in use, because the checklist is the contract the visitor can see: fewer than 8 characters, or a common password, is Weak, and Strong needs every rule met. The rules are at least data-min-length characters (12), upper and lower case, a number, a symbol, and not a common password.

Swap in your own scorer. Name a global function in data-scorer, or assign PasswordStrengthMeter.scorer. It receives the value and returns a number from 0 to 4 or an object with a score property. zxcvbn returns exactly that:

Code snippet omitted: it ships with the download.

A non-empty value is always at least 1, so zxcvbn's 0 shows as Weak. Extend the common list with PasswordStrengthMeter.common.add('yourcompanyname'). Scoring in the browser is guidance for the visitor; enforce your policy on the server as well.

Events and API. Every change dispatches psm:change on the root with detail { score, label, rules, matches, valid }. A valid submit dispatches a cancelable psm:submit; call preventDefault() on it to send the form yourself. The root also carries root.passwordStrengthMeter with set(value, confirmValue), reset(), state() and destroy(), which removes every listener.

How It Works

Sweep in, drain out. Each .psm-fill is scaled on x from its left edge. When the score rises, only the segments not already full get a gsap.to(fill, { scaleX: 1 }), delayed one after another from the left; when it falls, the segments above the new score tween to 0 from the right. Every tween uses overwrite: true, so fast typing never leaves a segment stranded half full. The fill colour comes from data-level on the meter, so the status colours never enter JavaScript.

The rolling label. The label's text is replaced at once, then it tweens from yPercent: 100 (or -100 when weakening) to 0 while an aria-hidden ghost holding the old word tweens out the other way and removes itself. A fresh change kills any ghost still rolling before it starts.

Drawn ticks. Each tick's length is read once with getTotalLength() and used as its strokeDasharray; met tweens strokeDashoffset to 0, lost tweens it back. A round-capped dash of zero length still paints a dot, so a fully un-drawn tick is set to opacity: 0. The eye's slash and the confirm field's match tick use the same helper.

Using It With Your Own Design

  • The fills must carry no CSS transform. GSAP owns their scaleX; the script sets the empty start state, so nothing is pre-hidden without JavaScript.
  • .psm-strength is a one-line window with overflow: hidden and a fixed width; widen it if your words are longer than "Strong".
  • .psm-seg has overflow: hidden so a fill sweeping in stays inside its rounded track.
  • The labels, rule wording, button text and fonts are yours. Keep each rule's data-rule key; the text beside it is free.

Themes

One design at two token values, light (the default) and dark. Pick one by setting the attribute on <body> in the demo, or on the component's wrapper in your page:

Code snippet omitted: it ships with the download.

Nothing else changes: same markup, same script, and no theme name is read in JavaScript. The demo's toolbar toggle and the ?variant=dark URL parameter only set that attribute.

Every colour, radius and shadow is a custom property defined in both body[data-variant="..."] blocks in assets/style.css:

  • Ground and ink: --ground, --ground-2, --raised, --hover, --ink, --ink-2, --ink-3, --line, --line-strong, --shadow
  • Accent (focus ring, pressed eye, submit button only): --accent, --accent-ink, --accent-text, --accent-wash
  • Status (the meter and its label only): --danger-fill, --warn-fill, --ok-fill for segments and ticks, and --danger-text, --warn-text, --ok-text for the word, each measured at 4.5:1 or better on the ground
  • Controls: --seg-empty, --button-off, --button-off-ink
  • Type and radii: --font, --radius, --radius-sm, --radius-xs

To re-value it for your brand, change those properties in the two blocks. The accent here is #5e6ad2 with white text at 4.7:1; check that pair if you swap it, and update the submit button's hover colour in .psm-submit:hover to match. Weak is danger, Fair is warning, Good and Strong are success: strength is status, so the accent never appears on the meter.

Customisation

  • FILL, FILL_STEP, DRAIN, DRAIN_STEP, ROLL, DRAW and UNDRAW at the top of the init in assets/script.js are the durations and staggers.
  • ANNOUNCE_DELAY (900ms) is how long typing must pause before the strength is spoken.
  • The bit thresholds (30 / 45 / 60) are in defaultScore, and the common list is the COMMON array above it.
  • Change data-min-length and the length rule's wording together.

Requirements

  • GSAP 3.12+ (core only, no plugins)
  • No build step, no framework

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.

Your cart

Your cart is empty

The Vault £99

The Vault library, plus future additions to the library.