Skip to content

Password Input

Reveal toggle, Caps Lock warning, a strength meter that shows the rules — and why blocking paste makes accounts less secure, not more.

Also called Secret Field, Passphrase Input — in this system all of them are Password Input.

Live preview

Everything below is the real component. Change the controls, tab through it, and turn on Inspector Mode to read any value off the screen.

Playground

At least 12 characters, mixed case, and a number or symbol.

Strength: Fair50%
  • At least 12 characters
  • Upper and lower case
  • A number or symbol

Sign in vs. sign up

Two different fields wearing the same mask. Sign-in wants current-password and no meter; sign-up wants new-password, the rules, and no confirm field if a reveal toggle exists.

Sign in
Sign up

At least 12 characters.

Caps Lock warning

The single highest-value affordance on a sign-in form. The user cannot see what they typed, so an all-caps password is otherwise an unexplainable failure.

Caps Lock is on.

The meter must show the rules

A bar labelled "Weak" is a rejection. The same bar with three live checks is a set of instructions the user can act on without guessing.

Rules shown
Strength: Fair50%
  • At least 12 characters
  • Upper and lower case
  • A number or symbol
Bar only
Strength: Weak25%

A reveal toggle replaces the confirm field

Confirm exists because the user cannot see what they typed. Give them a reveal control and the second field is a second chance to make the same typo twice.

Reveal

Use the eye to check it.

Confirm field

Interaction states

Every state a user can put this component into, rendered side by side. If a state is missing here, it is missing in production too.

Empty
Masked
Focus
Caps Lock
Error
Disabled
Weak25%
Weak
Fair50%
Fair
Good75%
Good
Strong100%
Strong

Anatomy

Every part, every measurement, and the reason it is that number.

At least 12 characters.

Strength: Good75%

A masked field with a reveal toggle inside it, and a strength meter that appears only once the user has typed something.

  1. Mask characterBrowser default (•)

    Never a custom character. The native mask is what password managers and screen readers detect, and replacing it breaks both.

  2. Reveal toggle28px, inside the field

    Inside the border so it reads as part of the control. Its accessible name changes with the state — "Show password" / "Hide password" — and it is a toggle button, not a checkbox.

  3. Reveal timeoutOptional, 8–15s

    Re-masking after a delay protects a screen left unattended. Only defensible on shared devices; on a personal one it is an interruption.

  4. Caps Lock warningWarning tone, on focus

    Detected from the keyboard event’s modifier state, shown only while the field has focus. Shouting about Caps Lock on an unfocused field is noise.

  5. Strength meter4px bar + rule list

    Appears after the first character, never before. An empty field showing "Weak" is an accusation.

  6. Rule list12px, live per rule

    Each rule ticks as it is met. This is the part that teaches; the bar alone is a verdict with no appeal.

Design tokens used

Values are read live from the running stylesheet, so this table can never drift from the code. Click any value to copy it.

Color

TokenValueUsed for
--ds-surface-inset—Field fill
--ds-border-interactive—Idle border
--ds-accent—Focus border
--ds-warning-border—Caps Lock border
--ds-warning-text—Caps Lock message
--ds-danger—Weak strength
--ds-success—Strong strength and met rules
--ds-fg-muted—Reveal glyph and unmet rules

Spacing

TokenValueUsed for
padding-rightRoom for the reveal toggle

Radius

TokenValueUsed for
--radius-mdField corners

Typography

TokenValueUsed for
--text-captionRule list

Motion

TokenValueUsed for
--duration-fastMeter fill transition

Recommended sizes

Pick a size from this table. Do not invent a new one — a fourth height is how a design system starts dying.

SizeHeightPaddingRadiusTypeMin widthTouch targetWhen to use
Small32px0 36px 0 10px—13px——Re-authentication inside a dialog.
Medium36px0 40px 0 12px—15px——The default.
Large44px0 44px 0 14px—16px——Sign-in and sign-up pages, where the field is the page.
Reveal toggle28px———28px44px on coarse pointersInside the field, right-aligned, 6px from the edge.
Strength bar4px—full———Full field width, directly beneath it, with the label beside the value.

Do

✗ onPaste={(e) => e.preventDefault()}
✓ nothing — paste is the default
Allow pasteUsers who cannot paste abandon password managers and fall back to passwords they can remember. Blocking paste measurably lowers the strength of the passwords you receive.
sign in → autocomplete="current-password"sign up → autocomplete="new-password"
Use the right autocomplete tokencurrent-password fills the saved credential; new-password offers to generate one. The wrong token means the manager either does nothing or overwrites a good entry with a half-typed value.

Caps Lock is on.

Warn about Caps Lock while focusedThe user cannot see what they typed. Without the warning, an all-caps password is a failure with no visible cause — and they will retype it identically.
  • At least 12 characters
  • A number or symbol
  • Show the rules, not just a verdict"Weak" tells the user they failed. Three live checks tell them what to do next, which is the only thing they actually need.

    Don't

    onPaste → preventDefault → users type “Summer2024!” instead
    Do not block paste or autofillIt is the clearest case of security theatre in the whole system. The users it inconveniences are exactly the ones using a password manager, and they are the ones with strong passwords.
    Do not enforce composition rules on sign-inThe password already exists. Validating its shape at sign-in tells an attacker your rules and tells a legitimate user nothing they can act on.
    Do not ask for a confirm field when a reveal existsConfirm compensates for not being able to see. The reveal toggle removes that problem, and the second field mostly collects the same typo twice.

    Maximum 16 characters.

    Do not cap the lengthA maximum length signals the password is being stored in a way it should not be, and it breaks the long passphrases a manager generates.

    Accessibility

    Not a checklist to run at the end. These are the requirements the component was built from.

    1.3.5Identify Input PurposeAA2.1.1KeyboardA3.3.1Error IdentificationA3.3.8Accessible AuthenticationAA4.1.2Name, Role, ValueA

    Contrast

    • The mask dots owe 4.5:1. They are how a user counts what they typed, and browsers often render them lighter than the input colour.
    • The strength meter must not rely on colour alone — the label ("Weak", "Strong") carries the same information for the 8% who cannot distinguish red from green.
    • Met and unmet rules differ by icon as well as colour, for the same reason.
    • The reveal glyph at 16px owes 4.5:1 against the field fill.

    Keyboard

    TabReaches the field, then the reveal toggle. The toggle is a real button and must be reachable.
    Space / EnterToggles reveal. Focus stays on the toggle; it must not jump back to the field.
    ⌘ / Ctrl + VPastes. Never intercepted.
    EnterSubmits the form from the field, as in any single-line input.

    Screen readers

    • Announce the requirements before the user types, via aria-describedby. Discovering them through failure is the worst possible order.
    • The reveal toggle must announce its state: "Show password, toggle button, not pressed".
    • Caps Lock must be announced, not just coloured. It is the one warning where the affected user is least able to see what went wrong.
    • WCAG 2.2’s Accessible Authentication criterion means no cognitive test may be required to sign in — paste, autofill and a reveal toggle are how you satisfy it.

    Focus & touch

    • Toggling reveal must not move focus or reset the caret. The naive implementation swaps the input type and the caret jumps to the end — preserve selectionStart and selectionEnd across the swap.
    • The reveal toggle needs a 44px target, which usually means growing the field to 44px rather than shrinking the glyph. Set autocomplete correctly so the platform keychain offers to fill — on mobile that is how most people sign in, and a wrong token silently removes the option.
    AttributeApplied toNotes
    autocompleteThe fieldcurrent-password or new-password. This is a functional attribute, and 1.3.5 requires it.
    aria-pressedThe reveal toggleIt is a toggle button. Its label should also change — "Show password" / "Hide password".
    aria-describedbyThe fieldPoints at the requirements list, so the rules are read before the user types rather than after they fail.
    role="status"The Caps Lock warningPolite. It must be announced, not merely coloured, or the user who most needs it never learns.
    aria-live="polite"The strength labelDebounced to roughly every 500ms. Announcing on every keystroke is unusable.
    aria-invalidThe fieldOn a failed sign-in, set on the form rather than the field — you do not know which of the two was wrong, and guessing helps an attacker.

    Code

    Example usage

    tsx
    1import { Field, PasswordInput } from '@/ui/Input'23// Sign in: the password already exists, so no meter and no rules.4<Field label="Password">5  <PasswordInput autoComplete="current-password" name="password" />6</Field>78// Sign up: new-password lets the manager offer to generate one.9<Field10  label="New password"11  description="At least 12 characters, mixed case, and a number or symbol."12>13  <PasswordInput autoComplete="new-password" name="new-password" />14</Field>1516// Caps Lock: read the modifier state from the event, only while focused.17const [caps, setCaps] = React.useState(false)18<input19  onKeyUp={(e) => setCaps(e.getModifierState('CapsLock'))}20  onKeyDown={(e) => setCaps(e.getModifierState('CapsLock'))}21  onBlur={() => setCaps(false)}22/>2324// Preserve the caret across the type swap. The naive version sends the25// cursor to the end every time the user peeks.26function toggleReveal(el: HTMLInputElement) {27  const { selectionStart, selectionEnd } = el28  setRevealed((r) => !r)29  requestAnimationFrame(() => el.setSelectionRange(selectionStart, selectionEnd))30}

    Framework-free HTML

    html
    <div class="ds-field">
      <label for="pw">New password</label>
    
      <!-- Read BEFORE typing, not discovered through failure. -->
      <ul id="pw-rules">
        <li>At least 12 characters</li>
        <li>Upper and lower case</li>
        <li>A number or symbol</li>
      </ul>
    
      <div class="ds-password">
        <input
          id="pw"
          type="password"
          name="new-password"
          autocomplete="new-password"
          aria-describedby="pw-rules pw-strength"
        />
        <!-- No maxlength. No onpaste handler. Both are anti-features. -->
        <button type="button" aria-label="Show password" aria-pressed="false">
          <svg aria-hidden="true">…</svg>
        </button>
      </div>
    
      <p id="pw-strength" role="status" aria-live="polite">Strength: Good</p>
      <p id="pw-caps" role="status" aria-live="polite" hidden>Caps Lock is on.</p>
    </div>

    CSS

    css
    .ds-password { position: relative; }
    
    .ds-password input {
      inline-size: 100%;
      block-size: 36px;
      padding-inline: 12px 40px;         /* room for the reveal toggle */
      border: 1px solid var(--ds-border-interactive);
      border-radius: var(--radius-md);
      background: var(--ds-surface-inset);
      /* Never set -webkit-text-security or a custom mask character: it is what
         password managers and screen readers detect. */
    }
    
    .ds-password button {
      position: absolute;
      inset-inline-end: 6px;
      inset-block-start: 50%;
      translate: 0 -50%;
      inline-size: 28px;
      block-size: 28px;
      color: var(--ds-fg-muted);         /* 4.5:1 — it is a control */
    }
    
    .ds-password input[aria-invalid='true'] { border-color: var(--ds-danger-border); }
    .ds-password[data-caps='true'] input   { border-color: var(--ds-warning-border); }
    
    /* Colour is never the only signal: the label says "Weak" or "Strong" too. */
    .ds-strength__bar { block-size: 4px; border-radius: 999px; }
    .ds-strength__bar[data-level='1'] { background: var(--ds-danger); }
    .ds-strength__bar[data-level='4'] { background: var(--ds-success); }
    
    @media (pointer: coarse) {
      .ds-password input  { block-size: 44px; padding-inline-end: 48px; }
      .ds-password button { inline-size: 44px; block-size: 44px; }
    }

    Component API

    PasswordInput

    PropTypeDefaultDescription
    autoComplete*'current-password' | 'new-password'—Functional, not optional. The wrong value breaks password managers in a way users blame on themselves.
    size'sm' | 'md' | 'lg''md'The reveal toggle keeps a touch-sized target at every size.
    status'default' | 'error' | 'success' | 'warning''default'Warning is the Caps Lock state; error is a failed submission.
    revealTimeoutnumber—Milliseconds before re-masking. Only worth it on shared devices; elsewhere it is an interruption.
    disabledbooleanfalseRemoves the field from the tab order.

    Notes

    Professional tips

    • Do not enforce a maximum length. A cap signals the password is not being hashed, and it breaks the long passphrases managers generate.
    • Check against a breached-password list rather than adding composition rules. "Summer2024!" satisfies every rule most products ship and is in every wordlist.
    • On a failed sign-in, never say which field was wrong. "Email or password is incorrect" is the correct message, and it is also the kind one.
    • Offer the reveal toggle on sign-in too, not only sign-up. Typos happen more often on the field where there is no confirm.
    • Keep the requirements visible after submission fails. Hiding them at the moment of failure is the most common version of losing the instructions.

    Performance

    • Debounce strength calculation by about 150ms. A real strength estimator like zxcvbn is not free on every keystroke.
    • Load the strength library lazily, only on the sign-up route. It is a few hundred kilobytes that the sign-in page has no use for.
    • Never send the password anywhere for strength checking. Breach checks use k-anonymity — send the first five characters of the SHA-1 hash and nothing else.

    Common mistakes

    • Blocking paste, which pushes users off password managers and onto weaker passwords.
    • The wrong autocomplete token, so the manager fills nothing or overwrites a good entry.
    • A maximum length, which suggests the password is being stored rather than hashed.
    • A strength bar with no rules, which rejects without instructing.
    • The caret jumping to the end when the user toggles reveal.
    • Enforcing composition rules at sign-in, where the password already exists.
    • Custom mask characters, which break password-manager detection and screen-reader announcements.
    • A Caps Lock warning that is coloured but never announced.

    Real-world recommendations

    • NIST guidance has recommended against composition rules and forced rotation for years. Length and a breach check outperform every symbol requirement ever shipped.
    • The confirm field is a hangover from before reveal toggles existed. Products that removed it and kept the toggle report fewer failed sign-ups, not more.
    • Password managers now fill the overwhelming majority of credentials. Every design decision here should be checked against "does this still work when a manager fills it?".
    • Passkeys are replacing this component. Where you support them, offer the passkey first and keep the password field as the fallback — not the other way round.