ViraStack Password: An Accessible Password Hook for React
A "show / hide" button on a password field looks like a few lines of useState. Then the list grows: a screen reader has to hear the button's state, the password should hide on submit so a password manager saves the right value, and someone typing with Caps Lock on should be told. On top of that you want to rate the password, check the confirmation and run the same rules on the server.
ViraStack Password (@virastack/password) puts all of that into one headless React hook: it hands you props for the input and the button, and the markup stays yours.
What it does
- Accessible visibility: Adds
aria-pressedto the button, hides the password on submit and reports Caps Lock. - NIST-style strength: The default policy asks for 15–64 characters and no composition rules; the level grows with length. It catches well-known passwords, repetition and personal information.
- Confirmation:
matchWithchecks that a second field repeats the first. - Same rules on the server:
@virastack/password/serverruns the same policy in a Server Action, and can check Have I Been Pwned for leaked passwords. - Zero dependencies: The hook is about 3 KB.
Try it
The fields use the default policy. The lines under them are what the hook returns: the level, the error keys and whether the two fields match.
Install
npm install @virastack/passwordUsage
For a plain show / hide toggle, call the hook with no arguments:
import { useViraPassword } from "@virastack/password"
export function PasswordField() {
const { inputProps, btnProps } = useViraPassword()
return (
<div className="relative">
<input {...inputProps} placeholder="Your password" />
<button {...btnProps} />
</div>
)
}In a sign-up form, bind it to React Hook Form, one entry per field:
import { definePolicy } from "@virastack/password/strength"
const policy = definePolicy() // NIST: 15–64 characters
const { password, confirm } = useViraPassword(form, {
password: { policy, forbidden: ["email"] },
confirm: { matchWith: "password" }
})
<input {...password.inputProps} />
<p {...password.strengthProps}>{password.strength?.level}</p>
<input {...confirm.inputProps} />How strength is measured
Length sets the level, not character classes. Real results with the default policy:
import { definePolicy, evaluate } from "@virastack/password/strength"
const policy = definePolicy()
evaluate("Summer2026!", policy)
// { level: "weak", valid: false, issues: ["password.tooShort", "password.common"] }
evaluate("correct horse battery staple", policy)
// { level: "weak", valid: false, issues: ["password.common"] }
evaluate("cold coffee on a quiet morning", policy)
// { level: "strong", valid: true, issues: [] }"Summer2026!" has every character class and is still weak: short, and a pattern everyone uses. The famous "correct horse battery staple" is long enough but too well known. A long lowercase phrase you can remember is strong, which is NIST's point.
Server validation, the leaked password check, every option and the error keys are in the virastack.com/password docs, and the source is in github.com/virastack/form. For masked fields in the same form, see ViraStack Mask.
