Skip to content

feat(input-password): add a password field with a caller scored meter - #231

Merged
ndlabdev merged 2 commits into
devfrom
feat/228-input-password
Sep 27, 2026
Merged

ndlabdev merged 2 commits into
devfrom
feat/228-input-password

Conversation

@ndlabdev

Copy link
Copy Markdown
Owner

Summary

Requested in #228. I declined it first and then reversed, because the two objections I raised did not hold up: ThemeModeButton is 89 lines and does less, so thinness is not where this library draws its line, and the two extra bundled icons measure at roughly half a kilobyte before compression. What the reporter got right is that the cost is not writing those lines once, it is keeping them identical across projects.

Closes #228

Note

Stacked on #230, which fixes the Input slots this component relies on. The base retargets to dev once that merges. The diff here is InputPassword only.

Type of change

  • 🐛 Bug fix
  • ✨ New feature / component
  • 📖 Documentation
  • ♻️ Refactor / chore
  • ⚠️ Breaking change

Changes

  • New InputPassword, wrapping Input rather than reimplementing it the way InputTags and InputNumber do. Those two reimplement because they change what sits inside the field box; this one only needs a trailing control and blocks below, so wrapping is enough. FormField and Form integration comes for free: label association, error state and aria-describedby all flow through, verified in a browser.
  • Adds passwordShow and passwordHide to the icon defaults and regenerates the bundled set, 25 icons to 27.

What it handles

  • Visibility toggle. The accessible name describes the next action rather than the current state, aria-pressed reports the state, and the button is inert while the field is disabled or loading. Its size follows the field, including when a size is set through global config.
  • Purpose driven autocomplete. login gives current-password, signup and change give new-password. Getting this wrong fails quietly: a sign up form marked current-password stops a password manager offering a generated password and may fill an existing one.
  • Caps lock warning. Read from the keyboard event, cleared on release or blur.
  • Paste is never blocked. No paste handler exists, and a test asserts the event is not cancelled.

Scoring stays with the caller

strength takes a number the caller computed. The component renders the meter, picks the colour and debounces the screen reader announcement so it fires on level changes rather than on every keystroke.

A naive scorer is worse than no meter. Measured, counting character classes against a length first approach:

password                       character classes   length first
Password1!                     Strong              Weak
correct horse battery staple   Good                Strong
qwerty123                      Good                Weak

Password1! is in every attack dictionary, and a long passphrase is punished for having no digit. Any rule set would be wrong for someone, and scoring properly needs a dictionary or a breach list, neither of which belongs in a UI library.

Checklist

  • Linked the related issue (Closes #228)
  • pnpm check passes (0 errors, 0 warnings)
  • pnpm lint passes
  • pnpm test passes
  • Added or updated tests for the change
  • Updated CHANGELOG.md under [Unreleased]
  • Followed component conventions (no comments outside *.types.ts, Material 3 design tokens)

Notes

37 tests. They cover the toggle in both directions and its accessible name, aria-pressed, the inert states, all three purpose values, paste not being cancelled, six caps lock cases including the warning appearing and clearing, the meter present and absent, score clamping at both ends, custom levels, the live region existing only alongside a meter, size staying in step with the field across three scenarios including a config override, ui.root applying exactly once while field slots still forward, the footer snippet, and ref.

Two defects were found and fixed during review rather than shipped. ui.root was landing on two elements, because both slot sets have a root key and ui was forwarded wholesale; the component now filters its own slot names before passing the rest down. The toggle also fell out of step with the field whenever a size was set through global config, since each resolved its default separately; size is now resolved once and shared.

The caps lock tests initially passed while testing nothing: KeyboardEvent takes no modifierStates option, so getModifierState returned false and only the off path was covered. They now patch getModifierState on the event and exercise the real path.

Tests: 3909 passing across 108 files.

Requested in #228. The first answer was to decline and document the
composition instead, which was wrong on two counts: ThemeModeButton is
89 lines and does less, so thinness is not where this library draws its
line, and the two extra bundled icons measure at about half a kilobyte
before compression.

The component wraps Input rather than reimplementing it the way
InputTags and InputNumber do, because those two need to change what sits
inside the field box while this one only needs a trailing control and
blocks below. FormField and Form integration comes for free as a result:
label association, error state and aria-describedby all flow through.

What it handles: a visibility toggle whose accessible name follows the
next action, purpose mapping to the correct autocomplete value for sign
in, sign up and password change, a caps lock warning cleared on release
or blur, and a guarantee that pasting is never intercepted.

Scoring stays with the caller. strength takes a number and the component
renders the meter, picks the colour and debounces the screen reader
announcement so it fires on level changes rather than on every
keystroke. A naive scorer is worse than no meter: counting character
classes rates Password1! as strong while penalising a long passphrase
for having no digit, and scoring properly needs a dictionary or a breach
list, neither of which belongs in a UI library.

Adds passwordShow and passwordHide to the icon defaults and regenerates
the bundled set.

Closes #228
@ndlabdev ndlabdev added the enhancement New feature or request label Sep 27, 2026
@ndlabdev ndlabdev self-assigned this Sep 27, 2026
@ndlabdev
ndlabdev changed the base branch from fix/229-input-slots to dev September 27, 2026 07:12
@ndlabdev
ndlabdev merged commit 4e7e9cc into dev Sep 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant