InputPassword
A password field that borrows Input's whole box and adds the three things a password needs: a reveal toggle that knows a tap from a peek, a caps-lock warning inside the field, and a timer that can put the secret back.
- Stable
- React 19
- Native <input type="password">
- Caps Lock warning
- Form-library ready
At least 8 characters.
Code
<InputPasswordname="password"label="Password"helper="At least 8 characters."/><div class="wayra-label" data-placement="top" data-size="md"><label class="wayra-label-text" for="usage-pw">Password</label><div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="md" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" value="cordillera-2026" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" id="usage-pw" aria-describedby="usage-pw-hint" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"></path><circle cx="12" cy="12" r="3.2"></circle></svg></button></div><p class="wayra-label-hint" id="usage-pw-hint">At least 8 characters.</p></div>label, helper and error are the only props that change the rendered structure, exactly
as on Input: pass none and you get the bare field, pass any one and the
component wraps it in the label contract.
Why it wraps rather than composes
Section titled “Why it wraps rather than composes”Input renders a bare, styled <input> — it has no suffix slot, and giving it one would
change the markup contract of every field in the library. So this component does not compose
Input; it reuses its contracts. The root <div> joins the same
styles/families/field.css family, which hands it the border, the fill, the radius, the height, the
focus border and the five size steps for the price of a few list memberships.
<div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="md" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" autocomplete="current-password" … /> <!-- the caps-lock badge appears here while the key is on --> <span class="wayra-input-password-status" role="status"></span> <button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password">…</button></div>That is the same shape ComboBox takes, and it is why the box is provably identical to an Input’s at every size — a story compares the two computed styles step by step.
The three reveal gestures
Section titled “The three reveal gestures”revealMode picks one behaviour out of the three people expect from an eye icon.
Code
<InputPassword revealMode="press" aria-label="press" /><InputPassword revealMode="hold" aria-label="hold" /><InputPassword aria-label="both" /><div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="md" data-shape="rounded" data-visible="true" data-holding="false"> <input class="wayra-input-password-input" type="text" value="cordillera-2026" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="press" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="true" aria-label="Hide password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4l16 16"></path><path d="M9.9 5.9A9.6 9.6 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-3.3 4"></path><path d="M6.6 7.8A16.6 16.6 0 0 0 2.5 12S6 18.5 12 18.5c1.4 0 2.7-.3 3.8-.8"></path><path d="M9.9 9.9a3.2 3.2 0 0 0 4.3 4.3"></path></svg></button></div><div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="md" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" value="cordillera-2026" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="hold" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"></path><circle cx="12" cy="12" r="3.2"></circle></svg></button></div><div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="md" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" value="cordillera-2026" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="both" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"></path><circle cx="12" cy="12" r="3.2"></circle></svg></button></div>revealMode |
Pointer down | Release | Space / Enter |
|---|---|---|---|
press |
nothing | click toggles and the state persists | toggles |
hold |
reveals | always hides | reveals while held, hides on key up or when focus leaves the toggle |
both (default) |
reveals immediately | under holdThreshold it was a tap and toggles; over it, it was a peek and the starting state returns |
toggles |
holdThreshold is the millisecond line between a tap and a peek, and it is read in both
alone. holdThreshold={0} turns every press into a peek, which is a quick way to get hold
semantics with press keyboard behaviour.
The pointer half uses setPointerCapture, so a press that drifts off the button still ends on
that button. mousedown is prevented, so the caret never leaves the field, and the gesture
puts focus back with the caret at the end of the value.
The static previews on this page can only click. These three are the component itself — tap an eye, then hold one down and watch the difference:
A click toggles, and it stays where you left it.
Revealed only while the eye is held down.
A tap toggles; hold past 250 ms and it is a peek. Auto-hides 5 s after a tap.
Code
import { InputPassword } from "wayra-ui";
// One value across the three, so the only difference you can see is the// gesture. Nothing here is a real password.const VALUE = "cordillera-2026";
export function GesturesDemo() { return ( <div className="hu-demo-col"> <InputPassword revealMode="press" label="press" helper="A click toggles, and it stays where you left it." defaultValue={VALUE} /> <InputPassword revealMode="hold" label="hold" helper="Revealed only while the eye is held down." defaultValue={VALUE} /> {/* The default mode, plus the timer from Security defaults: a peek is never cut in half, because the countdown does not run while the button is held. */} <InputPassword label="both" helper="A tap toggles; hold past 250 ms and it is a peek. Auto-hides 5 s after a tap." defaultValue={VALUE} revealTimeout={5000} /> </div> );}Security defaults
Section titled “Security defaults”Three behaviours are on before you configure anything, and all three exist because a revealed password is a password on somebody’s screen.
Code
<InputPassword revealTimeout={5000} aria-label="Auto-hides after 5 s" /><InputPassword aria-label="Hides when focus leaves" /><div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="md" data-shape="rounded" data-visible="true" data-holding="false"> <input class="wayra-input-password-input" type="text" value="cordillera-2026" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="Auto-hides after 5 s" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="true" aria-label="Hide password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4l16 16"></path><path d="M9.9 5.9A9.6 9.6 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-3.3 4"></path><path d="M6.6 7.8A16.6 16.6 0 0 0 2.5 12S6 18.5 12 18.5c1.4 0 2.7-.3 3.8-.8"></path><path d="M9.9 9.9a3.2 3.2 0 0 0 4.3 4.3"></path></svg></button></div><div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="md" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" value="cordillera-2026" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="Hides when focus leaves" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"></path><circle cx="12" cy="12" r="3.2"></circle></svg></button></div>- Leaving the field hides. Focus that leaves both the input and its toggle puts the secret back, whether a tap or a key revealed it. Moving between the input and the toggle is not leaving. A pointer press still in flight owns the visibility until it ends.
revealTimeoutre-hides on a clock.0, the default, turns it off. The countdown does not run while the button is held, so a long peek is never cut in half.- Visibility is uncontrolled, on purpose. There is no
visibleprop and noonVisibleChange. A secret an outside store can reveal is a secret something else can leave revealed, anddefaultVisible(legacy parity) is as far as this goes.
Caps Lock
Section titled “Caps Lock”Half of all “wrong password” errors are a capital letter, not a wrong password. The warning
appears inside the field while the key is on and the field has focus, and it is announced as
well as drawn: an aria-hidden icon plus a clipped role="status" message.
The message region is mounted empty from the start and only its text changes. A status is
announced when its content changes, not when it is inserted already holding that content, so a
region that arrived together with the warning would usually stay silent. The badge comes and
goes; the region stays. Hand-written markup that drives the warning from its own script keeps
the empty <span class="wayra-input-password-status" role="status"> in place the same way.
Code
<InputPassword label="Password" /><div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="md" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" value="cordillera-2026" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="Caps Lock is on" /> <span class="wayra-input-password-caps"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 3l7 7h-4v5H9v-5H5l7-7zM9 17h6v4H9z"></path></svg></span><span class="wayra-input-password-status" role="status">Caps Lock is on</span> <button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"></path><circle cx="12" cy="12" r="3.2"></circle></svg></button></div>The state is read from event.getModifierState("CapsLock") on keydown and keyup. Keyup
matters more than it looks: releasing the CapsLock key itself is a keyup, so a keydown-only
reading would leave the warning on screen after the user switched it off. Blur clears it.
capsLockWarning={false} turns the whole thing off.
Autocomplete
Section titled “Autocomplete”autoComplete defaults to current-password, which is what a sign-in form wants. Change it
on the forms where it is wrong:
| Screen | autoComplete |
What it buys |
|---|---|---|
| Sign in | current-password (default) |
the manager offers the stored password |
| Sign up, change password | new-password |
the manager offers to generate one |
| A second “confirm” field | new-password |
the same generated value, not the stored one |
<InputPassword name="password" label="New password" autoComplete="new-password" /><InputPassword name="confirm" label="Confirm password" autoComplete="new-password" />Leaving current-password on a sign-up form is the common mistake: the manager fills the old
password into the new-password field and never offers to save the new one.
Variants, colors and sizes
Section titled “Variants, colors and sizes”The same four looks, eight roles and five steps Input ships, with the same meanings — a password field belongs to a form and has to match the fields around it.
Code
<InputPassword aria-label="outline" /><InputPassword variant="filled" aria-label="filled" /><InputPassword variant="soft" aria-label="soft" /><InputPassword variant="ghost" aria-label="ghost" /><div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="md" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" value="cordillera-2026" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="outline" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"></path><circle cx="12" cy="12" r="3.2"></circle></svg></button></div><div class="wayra-input-password" data-variant="filled" data-color="primary" data-size="md" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" value="cordillera-2026" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="filled" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"></path><circle cx="12" cy="12" r="3.2"></circle></svg></button></div><div class="wayra-input-password" data-variant="soft" data-color="primary" data-size="md" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" value="cordillera-2026" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="soft" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"></path><circle cx="12" cy="12" r="3.2"></circle></svg></button></div><div class="wayra-input-password" data-variant="ghost" data-color="primary" data-size="md" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" value="cordillera-2026" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="ghost" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"></path><circle cx="12" cy="12" r="3.2"></circle></svg></button></div>Code
<InputPassword color="primary" aria-label="primary" /><InputPassword color="secondary" aria-label="secondary" /><InputPassword color="accent" aria-label="accent" /><InputPassword color="neutral" aria-label="neutral" /><InputPassword color="info" aria-label="info" /><InputPassword color="success" aria-label="success" /><InputPassword color="warning" aria-label="warning" /><InputPassword color="error" aria-label="error" /><div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="md" data-shape="rounded" data-visible="true" data-holding="false"> <input class="wayra-input-password-input" type="text" value="primary" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="primary" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="true" aria-label="Hide password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4l16 16"></path><path d="M9.9 5.9A9.6 9.6 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-3.3 4"></path><path d="M6.6 7.8A16.6 16.6 0 0 0 2.5 12S6 18.5 12 18.5c1.4 0 2.7-.3 3.8-.8"></path><path d="M9.9 9.9a3.2 3.2 0 0 0 4.3 4.3"></path></svg></button></div><div class="wayra-input-password" data-variant="outline" data-color="secondary" data-size="md" data-shape="rounded" data-visible="true" data-holding="false"> <input class="wayra-input-password-input" type="text" value="secondary" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="secondary" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="true" aria-label="Hide password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4l16 16"></path><path d="M9.9 5.9A9.6 9.6 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-3.3 4"></path><path d="M6.6 7.8A16.6 16.6 0 0 0 2.5 12S6 18.5 12 18.5c1.4 0 2.7-.3 3.8-.8"></path><path d="M9.9 9.9a3.2 3.2 0 0 0 4.3 4.3"></path></svg></button></div><div class="wayra-input-password" data-variant="outline" data-color="accent" data-size="md" data-shape="rounded" data-visible="true" data-holding="false"> <input class="wayra-input-password-input" type="text" value="accent" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="accent" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="true" aria-label="Hide password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4l16 16"></path><path d="M9.9 5.9A9.6 9.6 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-3.3 4"></path><path d="M6.6 7.8A16.6 16.6 0 0 0 2.5 12S6 18.5 12 18.5c1.4 0 2.7-.3 3.8-.8"></path><path d="M9.9 9.9a3.2 3.2 0 0 0 4.3 4.3"></path></svg></button></div><div class="wayra-input-password" data-variant="outline" data-color="neutral" data-size="md" data-shape="rounded" data-visible="true" data-holding="false"> <input class="wayra-input-password-input" type="text" value="neutral" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="neutral" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="true" aria-label="Hide password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4l16 16"></path><path d="M9.9 5.9A9.6 9.6 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-3.3 4"></path><path d="M6.6 7.8A16.6 16.6 0 0 0 2.5 12S6 18.5 12 18.5c1.4 0 2.7-.3 3.8-.8"></path><path d="M9.9 9.9a3.2 3.2 0 0 0 4.3 4.3"></path></svg></button></div><div class="wayra-input-password" data-variant="outline" data-color="info" data-size="md" data-shape="rounded" data-visible="true" data-holding="false"> <input class="wayra-input-password-input" type="text" value="info" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="info" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="true" aria-label="Hide password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4l16 16"></path><path d="M9.9 5.9A9.6 9.6 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-3.3 4"></path><path d="M6.6 7.8A16.6 16.6 0 0 0 2.5 12S6 18.5 12 18.5c1.4 0 2.7-.3 3.8-.8"></path><path d="M9.9 9.9a3.2 3.2 0 0 0 4.3 4.3"></path></svg></button></div><div class="wayra-input-password" data-variant="outline" data-color="success" data-size="md" data-shape="rounded" data-visible="true" data-holding="false"> <input class="wayra-input-password-input" type="text" value="success" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="success" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="true" aria-label="Hide password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4l16 16"></path><path d="M9.9 5.9A9.6 9.6 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-3.3 4"></path><path d="M6.6 7.8A16.6 16.6 0 0 0 2.5 12S6 18.5 12 18.5c1.4 0 2.7-.3 3.8-.8"></path><path d="M9.9 9.9a3.2 3.2 0 0 0 4.3 4.3"></path></svg></button></div><div class="wayra-input-password" data-variant="outline" data-color="warning" data-size="md" data-shape="rounded" data-visible="true" data-holding="false"> <input class="wayra-input-password-input" type="text" value="warning" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="warning" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="true" aria-label="Hide password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4l16 16"></path><path d="M9.9 5.9A9.6 9.6 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-3.3 4"></path><path d="M6.6 7.8A16.6 16.6 0 0 0 2.5 12S6 18.5 12 18.5c1.4 0 2.7-.3 3.8-.8"></path><path d="M9.9 9.9a3.2 3.2 0 0 0 4.3 4.3"></path></svg></button></div><div class="wayra-input-password" data-variant="outline" data-color="error" data-size="md" data-shape="rounded" data-visible="true" data-holding="false"> <input class="wayra-input-password-input" type="text" value="error" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="error" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="true" aria-label="Hide password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4l16 16"></path><path d="M9.9 5.9A9.6 9.6 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-3.3 4"></path><path d="M6.6 7.8A16.6 16.6 0 0 0 2.5 12S6 18.5 12 18.5c1.4 0 2.7-.3 3.8-.8"></path><path d="M9.9 9.9a3.2 3.2 0 0 0 4.3 4.3"></path></svg></button></div>The role is the edge at full strength, and it reaches one more thing here: the eye takes the
accent while the value is revealed, which is the only in-field signal that the secret is on
screen. On filled the eye switches to -content instead — an accent icon on an accent fill
is invisible, the same call PinCode makes for its caret.
Code
<InputPassword size="xs" aria-label="xs" /><InputPassword size="sm" aria-label="sm" /><InputPassword size="md" aria-label="md" /><InputPassword size="lg" aria-label="lg" /><InputPassword size="xl" aria-label="xl" /><div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="xs" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" value="xs" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="xs" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"></path><circle cx="12" cy="12" r="3.2"></circle></svg></button></div><div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="sm" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" value="sm" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="sm" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"></path><circle cx="12" cy="12" r="3.2"></circle></svg></button></div><div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="md" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" value="md" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="md" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"></path><circle cx="12" cy="12" r="3.2"></circle></svg></button></div><div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="lg" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" value="lg" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="lg" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"></path><circle cx="12" cy="12" r="3.2"></circle></svg></button></div><div class="wayra-input-password" data-variant="outline" data-color="primary" data-size="xl" data-shape="rounded" data-visible="false" data-holding="false"> <input class="wayra-input-password-input" type="password" value="xl" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="xl" />
<button class="wayra-input-password-toggle" type="button" aria-pressed="false" aria-label="Show password"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"></path><circle cx="12" cy="12" r="3.2"></circle></svg></button></div>The toggle is sized like the trigger DatePicker and
ComboBox carry, max(24px, field height × 0.6), so a password
field and a picker in one form show the same button at every step. The max() is not
decoration: WCAG 2.5.8 asks for 24 × 24 CSS px of pointer target, and an xs field is about
21px tall at the default unit, so at that step the button keeps the floor and overhangs the
field slightly rather than shrinking with it. One story measures the floor at all five
steps, because axe’s target-size rule is off in the version this repo pins; another
measures the match.
name, ref, defaultValue and onChange all come from ComponentProps<"input">, so a
native form, TanStack Form or Formik work with no adapter.
react-hook-form is built in. Inside Form an InputPassword registers itself on name
alone, the same native register path Input takes:
const form = useForm({ defaultValues: { password: "" } });
<Form form={form} onSubmit={signIn}> <InputPassword name="password" label="Password" /> <Button type="submit">Sign in</Button></Form>The validation message arrives in the hint slot, the field freezes while the form submits —
and so does its toggle, because a live reveal button on a frozen row would be the only thing
still moving — and noRhf turns all of it off for one instance.
ref points at the inner <input>, never at the wrapper: that is the element react-hook-form
focuses for shouldFocusError and the element that carries the value.
API reference
Section titled “API reference”InputPasswordProps extends ComponentProps<"input"> minus the attributes the component
owns: size and color mean something else here, width and height go with the
field’s fixed width, and type and children are managed.
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
"outline" | "filled" | "soft" | "ghost" |
"outline" |
Input’s four looks, unchanged. |
color |
WayraColor |
"primary" |
Edge, focus ring, and the revealed eye. |
size |
"xs" | "sm" | "md" | "lg" | "xl" |
"md" |
The field ladder. |
shape |
"rounded" | "rectangle" | "pill" |
"rounded" |
Same three Input offers. |
revealMode |
"press" | "hold" | "both" |
"both" |
See the three gestures. |
holdThreshold |
number |
250 |
Milliseconds separating a tap from a peek. both only. |
revealTimeout |
number |
0 |
Milliseconds until a revealed value hides itself. 0 is off. |
defaultVisible |
boolean |
false |
Whether the field starts revealed. Focus leaving the field still hides it. |
capsLockWarning |
boolean |
true |
The in-field Caps Lock warning. |
showIcon |
ReactElement |
stroked eye | Icon for the “show” action. |
hideIcon |
ReactElement |
stroked crossed eye | Icon for the “hide” action. |
labels |
{ show?, hide?, capsLock? } |
English | Accessible names for the toggle and the warning. |
autoComplete |
string |
"current-password" |
See Autocomplete. |
label |
ReactNode |
— | Visible label. Wraps the field in the label contract. |
labelPlacement |
"top" | "left" | "floating" |
"top" |
Same three Input offers. |
helper |
ReactNode |
— | Persistent hint, wired into aria-describedby. |
error |
ReactNode |
— | Hint slot, role="alert", aria-invalid, red edge and ring. |
noRhf |
boolean |
false |
Opts this instance out of react-hook-form entirely. |
disabled |
boolean |
false |
Native. Takes the toggle with it. |
readOnly |
boolean |
false |
Native. The value can still be revealed — looking is not editing. |
unstyled |
boolean |
false |
Drops the design-system classes, keeps the data attributes. |
There is no visible / onVisibleChange pair, and no type: this field is a password field.
className and style land on the wrapper, because the wrapper is the visible box. Every
other native attribute — readOnly, required, minLength, onFocus, data-* of your own
— goes to the inner <input>, and so does ref.
Accessibility
Section titled “Accessibility”| Key | Behavior |
|---|---|
Tab |
Two stops: the field, then its toggle. Moving between them keeps a revealed value; leaving both hides it. |
Space / Enter on the toggle |
press and both toggle; hold reveals while held. |
| Everything else in the field | Entirely native. |
- The toggle is a real button with
aria-pressed, anaria-labelthat names the action available (a hidden field offers “Show password”), andaria-controlspointing at the input once the field has an id. A bare field invents no id, so the attribute is simply absent there rather than dangling. - The icon names the action too, not the state: a hidden field draws the open eye. That is the rule the legacy component used, and reversing it is the single most common bug in a password toggle.
- Auto-repeat is ignored. Holding
Enteron the toggle would otherwise flash the value at the keyboard repeat rate. - The caps-lock warning is announced, not only drawn — see Caps Lock.
- Edge’s own reveal button is suppressed with
::-ms-reveal. Two eyes in one field is one too many, and the browser’s answers to none of the props above. - Windows high contrast repaints the eye from
CanvasTextand redraws both focus indicators as outlines, becausebox-shadowis dropped in that mode. - Reduced motion zeroes the component’s own durations rather than removing the
animations, which is the
states.csspattern: set a variable, never the property.
Do and don’t
Section titled “Do and don’t”Do
One field, one toggle. type="password" is real, so the value is never announced and the manager knows what it is.
Don't
A plain text field with the characters hidden by CSS. The secret is readable by a screen reader and by anything that reads the DOM.
Do
new-password on a sign-up form. The password manager offers to generate one and to save what the user ends up with.
Don't
current-password left in place on the same form. The manager fills the old password in and never offers to save the new one.
Do
The warning before the submit. The user fixes a capital letter instead of reading “wrong password”.
Don't
Wrong password.
The same typed value, rejected after a round trip. Nothing on screen says which key is to blame.