Password

.field-control[data-ui="password"]

Password reveal — the "show password" toggle.

How this differs from what it looks like

Not input (.input-password)

They are two halves of one field and you usually want both. .input-password is the look: a monospace face, so the row of masked bullets sits on the middle of the line instead of against the bottom of the box, and so the revealed value is legible character by character. password is the reveal button — the thing that swaps type="password" for type="text" and back. It is separate because it is the only part that cannot be CSS: an attribute has to change, so it costs a script, and a field that never reveals its value should not pay for one. Use .input-password alone for a field that stays masked; add data-ui="password" when there is a button to show it.

Examples

Show what you typed

View “Show what you typed” as
<div class="field">
  <label class="field-label" for="pw-demo">Password</label>

  <div class="field-control" data-ui="password">
    <input class="input input-password" id="pw-demo" name="password" type="password"
           value="correct-horse-battery" autocomplete="current-password" />
    <button class="btn btn-quiet btn-icon field-action" type="button"
            aria-pressed="false" aria-controls="pw-demo">
      <svg class="icon field-action-off" aria-hidden="true"><use href="#icon-eye"></use></svg>
      <svg class="icon field-action-on" aria-hidden="true"><use href="#icon-eye-off"></use></svg>
      <span class="sr-only">Show password</span>
    </button>
  </div>
</div>

Eleven lines of behaviour, and they exist because the thing that has to change is an attribute: CSS cannot write type.

The two CSS-only tricks are both worse than a script — a second type="text" input toggled by :checked submits two values and gives password managers two fields to fight over, and -webkit-text-security is one engine’s property that reveals nothing anywhere else. The button’s name stays "Show password" in every state; aria-pressed is what changes, because a control whose accessible name moves under you is one a screen reader user has to re-read on every press. Without the module the field is still a complete, labelled, submittable password input and the button simply does nothing — an omission, not a false promise, which is why the markup is safe to ship either way.

Tokens 0

Level 2, declared on .password itself. Set any of them on that selector to restyle this component without touching the skin.

None. It reads the skin's tokens directly.

Variants and states

Variants

None. It is one shape, and the page does the rest.

State it reads

None.

Before you ship it

This one needs a script. Opt the markup in with data-ui="password" and import import 'mostlycss/js/components/password' — that specifier and not the package root, which is a bundle carrying a second copy of the registry, so a page holding both initialises every element twice. What you lose without the script is listed below — for some components that is only polish, for others it is the whole interaction.

What you have to do 4 requirements

  1. Put data-ui="password" on the .field-control wrapper, the one element that holds both the input and the button and therefore knows they belong together.
  2. Give the button a constant name and let aria-pressed carry the state. "Show password" before and after; a control whose name changes under a screen reader user is a control they cannot find again.
  3. Point aria-controls at the input's id.
  4. Leave the button out if you are not importing this module. The field stays complete and submittable without it; a button that does nothing is not.

src/js/components/password.ts · npx mostlycss add password