Components
Password input
A password field with a button that shows and hides what was typed, built on input group.
<div class="field w-full max-w-sm">
<label class="label" for="pw-demo">Password</label>
<div class="input-group password-input" data-password-input>
<input class="input" id="pw-demo" type="password" name="password" autocomplete="current-password" value="correct horse battery" />
<span class="input-group-addon input-group-addon-inline-end">
<button type="button" class="btn btn-ghost btn-icon password-toggle" data-password-toggle aria-label="Show password" aria-pressed="false">
<span class="password-toggle-show"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/></svg></span>
<span class="password-toggle-hide"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/></svg></span>
</button>
</span>
</div>
</div>
Markup#
- Wrap a
type="password".inputin an input group with.password-inputanddata-password-input. - Put the toggle in an
.input-group-addon-inline-end: a.btn.btn-ghost.btn-icon.password-togglewithdata-password-toggle, holding.password-toggle-show(the eye) and.password-toggle-hide(the eye crossed out). - Give it
type="button"so it never submits the form, andaria-label="Show password".
The behaviour switches the input between type="password" and type="text" and sets
aria-pressed on the button, which swaps the icons. The label stays "Show password": screen readers say
"Show password, toggle button, pressed" once it's on, the pattern assistive technology expects. The caret and any
selection stay where they were, and tapping the toggle while typing keeps focus (and the phone keyboard) in the input.
Text toggle#
Words instead of an icon: drop .btn-icon and write Show and Hide in the two spans. They share one space, so the button doesn't change width.
<div class="field w-full max-w-sm">
<label class="label" for="pw-text">Password</label>
<div class="input-group password-input" data-password-input>
<input class="input" id="pw-text" type="password" name="password" autocomplete="current-password" />
<span class="input-group-addon input-group-addon-inline-end">
<button type="button" class="btn btn-ghost password-toggle" data-password-toggle aria-label="Show password" aria-pressed="false">
<span class="password-toggle-show">Show</span>
<span class="password-toggle-hide">Hide</span>
</button>
</span>
</div>
</div>
Checkbox#
A checkbox with data-password-toggle anywhere inside the wrapper
works as the toggle too. Here one checkbox shows both fields of a new-password form:
<div class="password-input flex w-full max-w-sm flex-col gap-4" data-password-input>
<div class="field">
<label class="label" for="pw-new">New password</label>
<input class="input" id="pw-new" type="password" name="new-password" autocomplete="new-password" data-password />
</div>
<div class="field">
<label class="label" for="pw-confirm">Confirm password</label>
<input class="input" id="pw-confirm" type="password" name="confirm-password" autocomplete="new-password" data-password />
</div>
<div class="field field-horizontal">
<input class="checkbox" type="checkbox" id="pw-show-both" data-password-toggle />
<label class="field-label" for="pw-show-both">Show passwords</label>
</div>
</div>
Every input with data-password (or type="password") inside the wrapper is shown and hidden
together.
Caps Lock warning#
Elements with data-password-caps in the field are shown only while Caps Lock is on as the user types in
the password, and hidden again when it goes off or the input loses focus. Try it:
Caps Lock is on.
<div class="field w-full max-w-sm">
<label class="label" for="pw-caps">Password</label>
<div class="input-group password-input" data-password-input>
<input class="input" type="password" name="password" id="pw-caps" autocomplete="current-password" autocapitalize="off" spellcheck="false" />
<span class="input-group-addon input-group-addon-inline-end">
<button type="button" class="btn btn-ghost btn-icon password-toggle" data-password-toggle aria-label="Show password" aria-pressed="false">
<span class="password-toggle-show"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/></svg></span>
<span class="password-toggle-hide"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/></svg></span>
</button>
</span>
</div>
<p class="field-warning" data-password-caps hidden><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4M12 17h.01"/></svg> Caps Lock is on.</p>
</div>
States#
As with any input group, aria-invalid="true" on the input turns
the border red and disabled fades the whole field; a disabled input disables its toggle. Pair them with
field messages:
That password is not right.
At least 12 characters.
<div class="field w-full max-w-sm self-center">
<label class="label" for="pw-invalid">Password</label>
<div class="input-group password-input" data-password-input>
<input class="input" type="password" name="password" id="pw-invalid" autocomplete="current-password" autocapitalize="off" spellcheck="false" value="hunter2" aria-invalid="true" aria-describedby="pw-invalid-error" />
<span class="input-group-addon input-group-addon-inline-end">
<button type="button" class="btn btn-ghost btn-icon password-toggle" data-password-toggle aria-label="Show password" aria-pressed="false">
<span class="password-toggle-show"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/></svg></span>
<span class="password-toggle-hide"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/></svg></span>
</button>
</span>
</div>
<p class="field-error" id="pw-invalid-error"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6M9 9l6 6"/></svg> That password is not right.</p>
</div>
<div class="field w-full max-w-sm self-center">
<label class="label" for="pw-new-help">New password</label>
<div class="input-group password-input" data-password-input>
<input class="input" type="password" name="new-password" id="pw-new-help" autocomplete="new-password" autocapitalize="off" spellcheck="false" minlength="12" aria-describedby="pw-new-help-text" />
<span class="input-group-addon input-group-addon-inline-end">
<button type="button" class="btn btn-ghost btn-icon password-toggle" data-password-toggle aria-label="Show password" aria-pressed="false">
<span class="password-toggle-show"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/></svg></span>
<span class="password-toggle-hide"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/></svg></span>
</button>
</span>
</div>
<p class="field-description" id="pw-new-help-text">At least 12 characters.</p>
</div>
<div class="field w-full max-w-sm self-center">
<label class="label" for="pw-disabled">Disabled</label>
<div class="input-group password-input" data-password-input>
<input class="input" type="password" name="password" id="pw-disabled" autocomplete="current-password" autocapitalize="off" spellcheck="false" value="secret" disabled />
<span class="input-group-addon input-group-addon-inline-end">
<button type="button" class="btn btn-ghost btn-icon password-toggle" data-password-toggle aria-label="Show password" aria-pressed="false">
<span class="password-toggle-show"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/></svg></span>
<span class="password-toggle-hide"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/></svg></span>
</button>
</span>
</div>
</div>
Browsers and password managers#
- Saving the password. Submitting the form hides the password again first, so browsers and password managers see a password field and offer to save it, and it isn't left on screen after an htmx swap.
- Autofill. Use
autocomplete="current-password"on sign-in forms and"new-password"on sign-up and change-password forms, so password managers fill the right one and suggest strong passwords. The macro setsautocapitalize="off"andspellcheck="false"too, so a shown password isn't capitalised or sent to a spell checker. - Edge's own eye. Microsoft Edge draws a reveal button inside password inputs;
.password-inputhides it so there is only one toggle. Safari's key icon for saved passwords stays.
With htmx#
The toggle is a type="button", so it never sends the form. The behaviour runs again on inputs htmx swaps
in, so a server re-render with an error keeps working. It also fires a bubbling
password-input:toggle with detail.visible on the wrapper.
Nunjucks macro#
The password_input macro writes the group, input and toggle.
{% from "components/password-input/password-input.html" import password_input %}
<div class="field">
<label class="label" for="password">Password</label>
{{ password_input("password", id="password", required=true, caps=true) }}
</div>
{{ password_input("new-password", autocomplete="new-password", toggle="text", minlength=12) }}
{{ password_input("password", invalid=true, attrs={"aria-describedby": "password-error"}) }}
Reference#
| Class / attribute | Description |
|---|---|
.password-input | On the .input-group. Add data-password-input. |
.password-toggle | The show / hide button. Add data-password-toggle. |
.password-toggle-show / .password-toggle-hide | Its look while hidden / while shown. |
data-password-toggle | On the button, or on a checkbox. |
data-password | Marks more inputs to show and hide together (default: type="password"). |
data-password-caps | Shown only while Caps Lock is on. |
data-visible | Set on the wrapper while the password is shown. |
password-input:toggle | Event on the wrapper with detail { visible }. |