Skip to content
KaUI is under active development. If you run into a bug, please open an issue.

Password Input

A password input with show/hide toggle, a strength indicator, and an optional rule checklist.

pnpm dlx shadcn@latest add @kaui/password-input
import {
PasswordInput,
PasswordInputRules,
PasswordInputStrengthChecker,
} from "@/components/ui/password-input";
<PasswordInput placeholder="Enter password">
<PasswordInputStrengthChecker />
</PasswordInput>

PasswordInput is a compound component. PasswordInputStrengthChecker and PasswordInputRules must be rendered as children , they read the current value through context. Both are optional and can be used independently or together.

The strength checker is powered by zxcvbn, which scores passwords from 0 to 4 based on estimated crack time , not just character rules. It detects keyboard patterns, common words, repeated characters, and dictionary entries.

ScoreLabelWhat it means
0Very weak

Instant crack e.g common words, 123456, keyboard walks

1Very weakGuessable with a short online attack
2WeakNeeds a targeted attack; not safe for sensitive data
3StrongResistant to most online attacks
4Very strongResistant to offline dictionary attacks

The four bars in the UI represent scores 1–4. Each bar fills with bg-primary when score > barIndex, and turns bg-destructive if the score is still below threshold. Once the score meets or exceeds threshold, all filled bars switch to bg-primary.

The default threshold is 3 , it requiring a “Strong” password before bars go green. Raise it to 4 for stricter requirements; lower it for less critical flows.

Color indicator and threshold is only use to relfect as ui indicator not handle rejective logic. It’s the job of onScoreChange to check whether password should be acceptance or not

Combine the strength indicator with a rule checklist to cover both entropy feedback and your specific requirements. Each rule is a plain { label, test } object , write the label in plain language and the test as a predicate over the password string. Rules turn from muted to primary as they pass.

Password strength
  • At least 8 characters
  • One uppercase letter
  • One lowercase letter
  • One number
  • One special character

Use onScoreChange to read the live score outside the component. The callback fires whenever the score changes. This can disable the submit button until the score meets your threshold.

You can also use the score inside PasswordInputRules by running zxcvbn directly in a rule’s test function:

import { ZxcvbnFactory } from "@zxcvbn-ts/core";
const zxcvbn = new ZxcvbnFactory({
/* your options */
});
const rules = [
{ label: "At least 8 characters", test: (p) => p.length >= 8 },
{ label: "One uppercase letter", test: (p) => /[A-Z]/.test(p) },
{ label: "Strong password", test: (p) => zxcvbn.check(p).score >= 3 },
];

Those customization concept below can also be found in zxcvbn-ts docs

The default strength checker loads English dictionaries. Use the options and userInputs props to go further.

These two props serve different purposes and have different lifetimes:

  • options : static configuration passed to the ZxcvbnFactory constructor. Read once at mount. Use it for custom dictionaries, keyboard graphs, and Levenshtein settings.
  • userInputs : dynamic per-user context passed to zxcvbn.check() on every evaluation. Memoize it so the score only recomputes when the values actually change.

The table below shows the three detection layers options enables:

What it catchesHowConfig

acmecorp in the password

Exact dictionary matchdictionary.banned: [“acmecorp”]

@cm3c0rp, acm3corp

l33t substitution always on for dictionary entries, no config needed

acmecorps, aacmecorp (near-misses)

Levenshtein distance on the full passworduseLevenshteinDistance: true
Password contains the user’s own name or emailPer-check user context

userInputs={[username, email]}

If options is provided without a translations key, the component automatically loads and merges English translations so raw keys like "userInputs" or "anotherWord" never appear. To customise specific messages, spread the English base and override only the keys you need:

import * as english from "@zxcvbn-ts/language-en";
import type { OptionsType } from "@zxcvbn-ts/core";
const OPTIONS: OptionsType = {
translations: {
...english.translations,
warnings: {
...english.translations.warnings,
userInputs: "Avoid using your name or email in your password.",
},
suggestions: {
...english.translations.suggestions,
anotherWord: "Add an uncommon word to make it harder to guess.",
},
},
dictionary: { banned: ["acmecorp", "welcome", "letmein", "admin"] },
useLevenshteinDistance: true,
levenshteinThreshold: 2,
};

The complete set of overridable keys:

TypeKeyDefault English text
warningstraightRowStraight rows of keys on your keyboard are easy to guess.
warningkeyPatternShort keyboard patterns are easy to guess.
warningsimpleRepeatRepeated characters like “aaa” are easy to guess.
warningextendedRepeatRepeated character patterns like “abcabcabc” are easy to guess.
warningsequencesCommon character sequences like “abc” are easy to guess.
warningrecentYearsRecent years are easy to guess.
warningdatesDates are easy to guess.
warningtopTenThis is a heavily used password.
warningtopHundredThis is a frequently used password.
warningcommonThis is a commonly used password.
warningsimilarToCommonThis is similar to a commonly used password.
warningwordByItselfSingle words are easy to guess.
warningnamesByThemselvesSingle names or surnames are easy to guess.
warningcommonNamesCommon names and surnames are easy to guess.
warninguserInputsThere should not be any personal or page related data.
warningpwnedYour password was exposed by a data breach on the Internet.
suggestionl33tAvoid predictable letter substitutions like ’@’ for ‘a’.
suggestionreverseWordsAvoid reversed spellings of common words.
suggestionallUppercaseCapitalize some, but not all letters.
suggestioncapitalizationCapitalize more than the first letter.
suggestiondatesAvoid dates and years that are associated with you.
suggestionrecentYearsAvoid recent years.
suggestionassociatedYearsAvoid years that are associated with you.
suggestionsequencesAvoid common character sequences.
suggestionrepeatedAvoid repeated words and characters.
suggestionlongerKeyboardPattern

Use longer keyboard patterns and change typing direction multiple times.

suggestionanotherWordAdd more words that are less common.
suggestionuseWordsUse multiple words, but avoid common phrases.
suggestionnoNeed

You can create strong passwords without using symbols, numbers, or uppercase letters.

suggestionpwnedIf you use this password elsewhere, you should change it.

Define OPTIONS outside the component so it is never recreated on re-renders. Use showFeedback={false} to hide the warning and suggestion block entirely.

The built-in English dictionaries miss passwords from other languages (hallo123, passwort, willkommen) and cannot check whether a password has appeared in a known data breach. The example below combines every available layer into a realistic account-creation form.

What it demonstrates:

  • Multi-language dictionaries : English and German word lists merged together. Install any @zxcvbn-ts/language-* package and spread its dictionary export into options.
  • Custom banned terms : company and product names added to the dictionary, detected with l33t substitution and Levenshtein automatically.
  • userInputs from form fields : username and email fed into every check() call via useMemo. Passwords containing personal info score low instantly.
  • Mock breach check : debounced async lookup after 700 ms. Four states: idle → checking → safe | breached.
  • Three gates on submit : the button only enables when score >= threshold AND breach === "safe" AND all explicit rules pass. Each gate is independent.
import * as common from "@zxcvbn-ts/language-common";
import * as english from "@zxcvbn-ts/language-en";
import * as german from "@zxcvbn-ts/language-de";
import type { OptionsType } from "@zxcvbn-ts/core";
const OPTIONS: OptionsType = {
translations: english.translations,
graphs: common.adjacencyGraphs,
dictionary: {
...common.dictionary,
...english.dictionary,
...german.dictionary, // catches: hallo, passwort, willkommen…
banned: ["acmecorp", "admin"],
},
useLevenshteinDistance: true,
levenshteinThreshold: 2,
};
PropTypeDefaultDescription
valuestring | numberControlled value. Omit to use uncontrolled mode
defaultValuestring | numberInitial value in uncontrolled mode
onChange(e: ChangeEvent<HTMLInputElement>) => voidCalled on every keystroke
childrenReactNodeSlot for sub-components or any additional content below the input

All other Input props are forwarded. The type prop is omitted — it is managed internally.

PropTypeDefaultDescription
threshold0 | 1 | 2 | 3 | 43

Minimum score considered acceptable. Bars at or above this score render in primary; below render in destructive

showFeedbackbooleantrue

Show or hide the warning and suggestion text below the bars. Set to false when the strength bars alone are sufficient feedback

onScoreChange(score: 0 | 1 | 2 | 3 | 4) => void

Called whenever the score changes. Use this to read the strength outside the component . For example, to disable a submit button until the score meets your threshold

userInputs(string | number)[]

Per-user context passed to every check() call , typically username and email. Passwords containing these values score lower. Updates reactively; memoize the array to avoid unnecessary re-evaluations

optionsOptionsTypeEnglish dictionaries

Static configuration for the strength engine , custom dictionaries, keyboard graphs, Levenshtein settings. Passed to the ZxcvbnFactory constructor; read once at mount. Define this outside the component so it is never recreated

Must be rendered inside PasswordInput.

PropTypeDefaultDescription
rulesPasswordRule[]Required. List of rules to display and evaluate

Each PasswordRule is { label: string; test: (password: string) => boolean }. The label is shown as-is in the checklist , write it in plain language your users will understand. The test predicate receives the current password and returns whether the rule passes.

Must be rendered inside PasswordInput.