Password - National Design System

A password field that checks strength rules as the user types, confirms a retyped password matches, and blocks the form submit until both pass

Strength Rules

Each rule is a chip in the markup. The chip turns green when the value passes and red when it fails, and any failing chip blocks the form submit.

Five built-in rules
<div class="nds-form-container nds-password"> <div class="nds-form-header"> <label for="password-rules-1"> <span class="nds-label">New password</span> </label> </div> <div class="nds-form-control"> <i class="nds-icon nds-hgi-lock-password" aria-hidden="true"></i> <input type="password" id="password-rules-1" name="new-password" class="nds-input" autocomplete="new-password" minlength="10" required aria-describedby="password-rules-1-list"> <div class="nds-form-action"> <button class="nds-btn nds-subtle nds-clear" hidden type="button" aria-label="Clear password"> <i class="nds-icon nds-hgi-cancel-01" aria-hidden="true"></i> </button> <button class="nds-btn nds-subtle nds-toggle-password" type="button" aria-label="Show password"> <i class="nds-icon nds-hgi-view-off" aria-hidden="true"></i> </button> </div> </div> <div class="nds-form-footer" data-feedback-target> <div class="nds-password-rules" id="password-rules-1-list"> <span class="nds-feedback nds-outline nds-sm" data-permanent data-status="neutral" data-rule="length"> <span class="nds-feedback-icon"><i class="nds-icon" aria-hidden="true"></i></span> <span class="nds-feedback-message">At least 10 characters</span> </span> <span class="nds-feedback nds-outline nds-sm" data-permanent data-status="neutral" data-rule="upper"> <span class="nds-feedback-icon"><i class="nds-icon" aria-hidden="true"></i></span> <span class="nds-feedback-message">One capital letter (A-Z)</span> </span> <span class="nds-feedback nds-outline nds-sm" data-permanent data-status="neutral" data-rule="lower"> <span class="nds-feedback-icon"><i class="nds-icon" aria-hidden="true"></i></span> <span class="nds-feedback-message">One small letter (a-z)</span> </span> <span class="nds-feedback nds-outline nds-sm" data-permanent data-status="neutral" data-rule="digit"> <span class="nds-feedback-icon"><i class="nds-icon" aria-hidden="true"></i></span> <span class="nds-feedback-message">One number (0-9)</span> </span> <span class="nds-feedback nds-outline nds-sm" data-permanent data-status="neutral" data-rule="special"> <span class="nds-feedback-icon"><i class="nds-icon" aria-hidden="true"></i></span> <span class="nds-feedback-message">One symbol (! @ # $ %)</span> </span> </div> <span class="nds-password-status" role="status" aria-live="polite"></span> </div> </div>

Confirm Password

Point a second field at the first with data-password-match. The submit stays blocked until the two values are equal. There are two patterns for showing the result, and both are canonical.

Change password form
<form class="nds-form" data-ajax> <label for="password-confirm-user" hidden aria-hidden="true">Username</label> <input type="text" id="password-confirm-user" autocomplete="username" hidden aria-hidden="true"> <div class="nds-form-container nds-password" data-required> <div class="nds-form-header"> <label for="password-confirm-1"> <span class="nds-label">New password</span> </label> </div> <div class="nds-form-control"> <i class="nds-icon nds-hgi-lock-password" aria-hidden="true"></i> <input type="password" id="password-confirm-1" name="new-password" class="nds-input" autocomplete="new-password" minlength="8" required aria-describedby="password-confirm-1-list"> <div class="nds-form-action"> <button class="nds-btn nds-subtle nds-clear" hidden type="button" aria-label="Clear password"> <i class="nds-icon nds-hgi-cancel-01" aria-hidden="true"></i> </button> <button class="nds-btn nds-subtle nds-toggle-password" type="button" aria-label="Show password"> <i class="nds-icon nds-hgi-view-off" aria-hidden="true"></i> </button> </div> </div> <div class="nds-form-footer" data-feedback-target> <div class="nds-password-rules" id="password-confirm-1-list"> <span class="nds-feedback nds-outline nds-sm" data-permanent data-status="neutral" data-rule="length"> <span class="nds-feedback-icon"><i class="nds-icon" aria-hidden="true"></i></span> <span class="nds-feedback-message">At least 8 characters</span> </span> <span class="nds-feedback nds-outline nds-sm" data-permanent data-status="neutral" data-rule="upper"> <span class="nds-feedback-icon"><i class="nds-icon" aria-hidden="true"></i></span> <span class="nds-feedback-message">One capital letter (A-Z)</span> </span> <span class="nds-feedback nds-outline nds-sm" data-permanent data-status="neutral" data-rule="digit"> <span class="nds-feedback-icon"><i class="nds-icon" aria-hidden="true"></i></span> <span class="nds-feedback-message">One number (0-9)</span> </span> </div> <span class="nds-password-status" role="status" aria-live="polite"></span> </div> </div> <div class="nds-form-container nds-password" data-required data-password-match="#password-confirm-1"> <div class="nds-form-header"> <label for="password-confirm-2"> <span class="nds-label">Retype new password</span> </label> </div> <div class="nds-form-control"> <i class="nds-icon nds-hgi-lock-password" aria-hidden="true"></i> <input type="password" id="password-confirm-2" name="retype-password" class="nds-input" autocomplete="new-password" required aria-describedby="password-confirm-2-list"> <div class="nds-form-action"> <button class="nds-btn nds-subtle nds-clear" hidden type="button" aria-label="Clear password"> <i class="nds-icon nds-hgi-cancel-01" aria-hidden="true"></i> </button> <button class="nds-btn nds-subtle nds-toggle-password" type="button" aria-label="Show password"> <i class="nds-icon nds-hgi-view-off" aria-hidden="true"></i> </button> </div> </div> <div class="nds-form-footer" data-feedback-target> <div class="nds-password-rules" id="password-confirm-2-list"> <span class="nds-feedback nds-outline nds-sm" data-permanent data-status="neutral" data-rule="match"> <span class="nds-feedback-icon"><i class="nds-icon" aria-hidden="true"></i></span> <span class="nds-feedback-message">Matches the new password</span> </span> </div> <span class="nds-password-status" role="status" aria-live="polite"></span> </div> </div> <div class="nds-form-actions"> <button type="submit" class="nds-btn nds-primary"> <span class="nds-label">Save password</span> </button> </div> </form>

Two Patterns

With a match chip. Use this when the confirm field sits beside strength rules, as the demo above shows. The chip joins the rule list and updates while the user types in either field, so the whole password state reads in one place.

Without a chip. Use this when confirm is the only check on the field. Set data-password-match and ship no rule list. The component still blocks the submit and still shows the mismatch message, because the match test does not depend on chips. This suits a short form where one more chip adds noise.

Do not add a chip only because the demo has one. Pick the pattern that matches the field's role.

Custom Rules

Add data-rule-pattern to a chip and the regular expression becomes the rule. The chip then counts and gates like a built-in one, with no JavaScript to write.

Rules from regular expressions
<div class="nds-form-container nds-password"> <div class="nds-form-header"> <label for="password-custom-1"> <span class="nds-label">New password</span> </label> </div> <div class="nds-form-control"> <i class="nds-icon nds-hgi-lock-password" aria-hidden="true"></i> <input type="password" id="password-custom-1" name="new-password" class="nds-input" autocomplete="new-password" minlength="12" required aria-describedby="password-custom-1-list"> <div class="nds-form-action"> <button class="nds-btn nds-subtle nds-clear" hidden type="button" aria-label="Clear password"> <i class="nds-icon nds-hgi-cancel-01" aria-hidden="true"></i> </button> <button class="nds-btn nds-subtle nds-toggle-password" type="button" aria-label="Show password"> <i class="nds-icon nds-hgi-view-off" aria-hidden="true"></i> </button> </div> </div> <div class="nds-form-footer" data-feedback-target> <div class="nds-password-rules" id="password-custom-1-list"> <span class="nds-feedback nds-outline nds-sm" data-permanent data-status="neutral" data-rule="length"> <span class="nds-feedback-icon"><i class="nds-icon" aria-hidden="true"></i></span> <span class="nds-feedback-message">At least 12 characters</span> </span> <span class="nds-feedback nds-outline nds-sm" data-permanent data-status="neutral" data-rule="nospace" data-rule-pattern="^\S+$"> <span class="nds-feedback-icon"><i class="nds-icon" aria-hidden="true"></i></span> <span class="nds-feedback-message">No spaces</span> </span> <span class="nds-feedback nds-outline nds-sm" data-permanent data-status="neutral" data-rule="twodigits" data-rule-pattern="\d.*\d"> <span class="nds-feedback-icon"><i class="nds-icon" aria-hidden="true"></i></span> <span class="nds-feedback-message">Two numbers or more</span> </span> </div> <span class="nds-password-status" role="status" aria-live="polite"></span> </div> </div>

Built-in Features

Auto-initialization

Activates on any form container that carries the nds-password class. Chips start checking on the first keystroke.

Five Built-in Rules

Length, capital letter, small letter, number, and symbol checks are ready to use. Ship a chip for each rule you want.

Rules From Markup

A regular expression on a chip becomes a working rule, so a project-specific policy needs no JavaScript.

Confirm Match

A retype field tracks the first field as the user types in either one, so a mismatch shows before submit.

Submit Gating

A failing rule or a mismatch blocks the submit through native constraint validation, with a message under the field.

Screen Reader Progress

A hidden live region reports how many rules are met, so the chips are not silent to a screen reader.

Strength Value in CSS

The container carries the number of passing rules, ready to drive a strength bar from a stylesheet.

Programmatic Control

Register rules, force a re-check, and read the current result through the JS API.

Usage Guidelines

Best Practices

  • Use this on sign-up, change-password, and password-reset forms, where the user picks a new value and needs to know the policy up front
  • Do not use it on a sign-in form. The user already has a password there, so a plain password field from Text Fields is enough
  • Ship one chip per rule the server enforces. A chip the server ignores teaches the user a policy that is not real
  • Keep minlength on the input. The browser enforces it on its own, so length still blocks the submit if the JavaScript never loads
  • Place nds-password-rules inside the data-feedback-target footer, and give every chip data-permanent. A validation message then takes over that slot and the chips come back when it clears
  • Prefer data-rule-pattern over addRule() for a one-off rule, so the expression sits next to the message the user reads
  • Anchor a pattern with ^ and $ when the whole value must match. Without anchors the expression matches anywhere in the value
  • Write each chip message as the rule itself, such as "One number (0-9)", not as an error. The chip turns red on its own when the rule fails
  • Give the retype field a match chip. Without it the mismatch only shows at submit
  • Add a hidden username field with autocomplete="username" above the password fields so password managers save the right credential
  • Do not treat the strength number as a security measure. It counts chips, not how hard the password is to guess

Structure Classes

ClassDescription
nds-passwordAdd to nds-form-container to opt the field into rule checking and match checking
nds-password-rulesWrapper for the rule chips, placed inside the data-feedback-target footer. Point the input's aria-describedby at it. The footer has no layout of its own, so this wrapper owns the chip spacing
nds-password-statusVisually hidden live region that receives the rules-met announcement. Optional, but a screen reader gets no live feedback without it

Data Attributes

AttributeDescription
data-password-matchSet on nds-form-container to a CSS selector for the source input. Makes this field a confirm field. Resolved once at init, so a source field added later needs NDS.Password.reinit()
data-ruleSet on a chip inside the container. Values: length, upper, lower, digit, special, match. Any other name needs data-rule-pattern or addRule(), otherwise the chip stays grey and is never counted
data-rule-patternSet on a chip to a regular expression. Overrides a built-in name of the same value. The expression matches anywhere in the value unless you anchor it. An invalid expression logs a warning and leaves the chip inert
data-statusSet on each chip to neutral in your markup. The component switches it to success or error per keystroke. The JavaScript loads after first paint, so a chip without a starting value has no style until then
data-permanentSet on each chip. A validation message in the same footer hides the chips instead of destroying them, and they return when the message clears. Without it the chips are dismissed for good on the first error
data-password-strengthWritten by the component on the container: the number of passing chips, and always 0 while the field is empty. Read it in CSS to drive your own strength bar, one rule per level, for example .nds-password[data-password-strength="5"] .my-strength-bar { width: 100%; }. NDS ships no meter element: the bar is yours
minlengthThe native HTML attribute on the input. Sets the threshold for the length rule. Defaults to 8 when absent

JavaScript API

The NDS.Password API wires containers, registers rules, and reports the current result. Fields already in the HTML are wired for you. Call NDS.Password.init() after adding a field to the page.

// ── Wire fields added after page load ──────────────── NDS.Password.init(); // scan the page, skip containers already wired NDS.Password.reinit(); // same as init() const field = document.querySelector('#signup-password').closest('.nds-form-container'); // ── Wire or unwire one field ───────────────────────── NDS.Password.create(field); // returns the instance, or null if it could not wire NDS.Password.destroy(field); // unwire, lift the submit block, reset the chips // ── Force a re-check now ───────────────────────────── const result = NDS.Password.check(field); // { strength: 4, allPass: false, rules: { length: true, upper: true, digit: false } } // ── Read the last result without re-checking ───────── field.ndsPassword.getStrength(); // ── Register a custom rule ─────────────────────────── // Chips with data-rule="nouser" start counting on the next keystroke. NDS.Password.addRule('nouser', function (value, ctx) { return value.toLowerCase() !== document.querySelector('#username').value.toLowerCase(); }); // ctx.minLength the input's minlength (8 when the attribute is absent) // ctx.matchValue the source field's value, or null when the container // has no data-password-match // ── React to every keystroke ───────────────────────── field.addEventListener('nds:password:change', function (e) { e.detail.strength; // number of passing chips, 0 while the field is empty e.detail.allPass; // true when every active chip passes, false while empty e.detail.rules; // { length: true, upper: false, digit: true } }); // The event carries no password value. Read the input when you need it.
Last Modified Date: 18/08/2026 - 01:37 AM
Was this page useful?
60% of users said Yes from 2843 Feedbacks