Skip to content

Password toggle

A password field in an input group with a .re-input-group__action button marked data-re-password-toggle. Opt into the behavior:

import { enhancePasswordToggle } from "@relements/core";
enhancePasswordToggle(document);

The button points at its field via aria-controls (or just shares the input group). Toggling flips input.type, reflects aria-pressed, sets data-revealed for icon swapping, and preserves focus and caret position. The accessible name stays "Show password" — a toggle button conveys on/off via aria-pressed, not by renaming itself.

Because the button only works with JavaScript (a password field has no native reveal), author it hidden; the enhancer un-hides it. So with no JavaScript the field stays a normal, fully usable password input and no dead control is shown.

The button holds two icons marked data-when="hidden" and data-when="shown"; the behavior toggles their hidden attribute.

Live example
<label class="re-field">
  <span class="re-field__label">Password</span>
  <span class="re-input-group">
    <input
      class="re-input"
      type="password"
      id="pw"
      value="hunter2"
      autocomplete="current-password"
    />
    <button
      class="re-input-group__action"
      type="button"
      data-re-password-toggle
      aria-controls="pw"
      aria-label="Show password"
      hidden
    >
      <svg
        data-when="hidden"
        viewBox="0 0 20 20"
        fill="none"
        stroke="currentColor"
        stroke-width="1.6"
        aria-hidden="true"
      >
        <path d="M1.5 10S4.5 4 10 4s8.5 6 8.5 6-3 6-8.5 6-8.5-6-8.5-6Z" />
        <circle cx="10" cy="10" r="2.5" />
      </svg>
      <svg
        data-when="shown"
        hidden
        viewBox="0 0 20 20"
        fill="none"
        stroke="currentColor"
        stroke-width="1.6"
        aria-hidden="true"
      >
        <path d="M1.5 10S4.5 4 10 4s8.5 6 8.5 6-3 6-8.5 6-8.5-6-8.5-6Z" />
        <circle cx="10" cy="10" r="2.5" />
        <path d="m3 3 14 14" />
      </svg>
    </button>
  </span>
</label>
  • Keyboard — the toggle is a native <button type="button">: Tab moves to it, Enter or Space activates it. The field stays a native password <input> with all its native behavior. Activating preserves focus and caret position so typing isn’t interrupted by the reveal.
  • Focus — both controls show the standard :focus-visible ring. The input group owns the outer ring via :focus-within; the action button gets an inset ring instead, so it stays inside the group’s clipped (overflow-hidden) rounded box.
  • Semanticsaria-controls ties the button to the input it reveals. State is conveyed by aria-pressed (false hidden, true shown) on a button whose accessible name stays "Show password" — assistive tech announces the pressed state rather than a renamed control. The two eye / eye-off <svg> icons are decorative (aria-hidden="true"); the behavior swaps them and never exposes them to the accessibility tree.
  • Notes — because a password field has no native reveal, the button is a JavaScript-only affordance authored hidden and un-hidden by the enhancer, so no JavaScript means no dead, announced control. See the accessibility guide.