|Kit

Password Input

A native password field with a built-in show / hide toggle, in the shared input shell — bordered box, focus ring, and invalid / disabled states, plus a built-in label, description, and error.

PasswordInput is a native password field with a built-in reveal toggle. It renders an <input type="password"> inside the same shell as Input and adds an inline-end button that flips the field between masked and visible. Like the rest of the family it carries its own label, description, error, and required — no external wrapper needed — and passing error flips it to the invalid (red) state. Omit them and it still composes cleanly inside a Field.

import { PasswordInput } from '@42-central/ui-react/password-input';

<PasswordInput name="password" placeholder="Enter a password" />

It is an event-based control, like Input: value, onChange, name, and ref forward to the underlying <input>, so React Hook Form's register and a plain onChange(event) handler both work as usual. The show / hide state is owned internally by default and never leaks into the form value; lift it out with visible when something else needs to drive it.

Playground

Component

Loading preview

Props

Code

<Component
  label="Password"
  size="md"
  placeholder="Enter a password"
/>

Variants

Two shells. default is bordered; unstyled strips the chrome but keeps the layout (height + the reveal toggle).

Code

<PasswordInput variant="default" placeholder="Default" />
<PasswordInput variant="unstyled" placeholder="Unstyled" />

Sizes

Five steps — xs through xl. The control font, padding, and the toggle icon scale to match Input.

Code

<PasswordInput size="xs" placeholder="xs" />
<PasswordInput size="sm" placeholder="sm" />
<PasswordInput size="md" placeholder="md" />
<PasswordInput size="lg" placeholder="lg" />
<PasswordInput size="xl" placeholder="xl" />

States

invalid swaps the border and focus ring to red regardless of color (and sets aria-invalid). disabled dims the shell and disables both the input and the toggle; readOnly keeps full contrast and stays focusable but rejects edits.

Code

<PasswordInput label="Invalid" invalid defaultValue="weak" />
<PasswordInput label="Disabled" disabled defaultValue="secret" />
<PasswordInput label="Read-only" readOnly defaultValue="secret" />

With label, description & error

label, description (helper text), and error are built in via Field — no external wrapper needed. Passing error flips the shell to the invalid (red) state; required adds the semantic flag and a red asterisk on the label.

At least 8 characters.
Password is too weak

Code

<PasswordInput label="Password" description="At least 8 characters." placeholder="Enter a password" />

<PasswordInput label="Password" required placeholder="Enter a password" />

<PasswordInput label="Password" error="Password is too weak" placeholder="Enter a password" />

If you omit these props, PasswordInput still composes cleanly inside a Field: disabled / invalid / readOnly / required propagate from the Field, and the aria-* relationships are wired for you.

import { Field } from '@42-central/ui-react/field';
import { PasswordInput } from '@42-central/ui-react/password-input';

<Field label="Password" description="At least 8 characters." required>
  <PasswordInput name="password" placeholder="Enter a password" />
</Field>

Controlled visibility

visible / defaultVisible / onVisibleChange lift the reveal state out of the field. A common case is one "Show passwords" checkbox for a password and its confirmation: drive both from shared state, and pass hideToggle so the per-field buttons don't fight the checkbox.

Code

const [visible, setVisible] = useState(false)

<PasswordInput label="Password" visible={visible} hideToggle />
<PasswordInput label="Confirm password" visible={visible} hideToggle />
<Checkbox
  label="Show passwords"
  checked={visible}
  onCheckedChange={(checked) => setVisible(checked === true)}
/>

Keep the built-in toggle and pass onVisibleChange instead when you only need to observe or veto a press. It receives the next state as a plain boolean, and a controlled field changes only once you feed that back into visible.

Custom toggle

renderToggle is called with the current state and renders inside the kit's button, so the aria-controls / aria-label wiring and the keyboard handling stay intact while you swap the icon. hideToggle renders no button at all.

Code

<PasswordInput
  label="Password"
  defaultValue="secret"
  renderToggle={(visible) => (visible ? <LockKeyholeOpen /> : <LockKeyhole />)}
/>

The button's accessible name still comes from translations, never from what renderToggle renders. An icon has no name of its own, so that is what you want. Visible text needs translations too: without it the button keeps the English "Show password", which overrides your "Afficher". That fails WCAG 2.5.3 Label in Name, because a voice-control user who says "click Afficher" hits nothing. Return a label that contains the visible text. In development the kit warns in the console when it doesn't.

Code

<PasswordInput
  label="Mot de passe"
  defaultValue="secret"
  renderToggle={(visible) => (visible ? 'Masquer' : 'Afficher')}
  translations={{
    visibilityTrigger: (visible) =>
      visible ? 'Masquer le mot de passe' : 'Afficher le mot de passe',
  }}
/>

Translations

The button's accessible label comes from translations.visibilityTrigger, called with the current state. It is the only text the component renders on its own. Keep it state-dependent, since the label is what tells AT whether the value is shown, and make it contain any text a custom renderToggle shows.

Code

<PasswordInput
  label="Mot de passe"
  translations={{
    visibilityTrigger: (visible) =>
      visible ? 'Masquer le mot de passe' : 'Afficher le mot de passe',
  }}
/>

API

Prop

Type

name, placeholder, id, ref, and onBlur forward to the underlying <input>. className styles the shell root. testId lands on that same <input> — see the Testing guide.

Accessibility

  • Pass label / description / error to wire the accessible name and aria-* relationships via the built-in Field. Omit them and it composes inside an outer Field (or your own <label htmlFor>) instead.
  • The reveal toggle is a real <button> with aria-controls pointing at the input. It is exposed to AT rather than nested in an aria-hidden decoration.
  • Its state lives in its label, which flips between "Show password" and "Hide password" (see Translations). It carries no aria-pressed or aria-expanded: revealing a value doesn't expand anything, and a pressed state on top of a label that already changes would read "Hide password, pressed". If you localize it, keep the label state-dependent.
  • The toggle is a tab stop right after the input, and Enter / Space toggle it like a click, keeping focus on the button. Pass toggleTabIndex={-1} to take it out of the tab order, for a form where keyboard users should go straight from field to field; it stays clickable.
  • A custom renderToggle renders inside that same button, so it keeps those attributes, including the aria-label, which overrides the content. If the content is visible text, pass translations whose label contains it (WCAG 2.5.3). With hideToggle, the control you replace it with owns the accessible name. A labelled checkbox works out of the box.
  • invalid sets aria-invalid on the input.

Changelog

v0.19.0
  • Control the reveal state with visible, defaultVisible and onVisibleChange(visible), for example one "Show passwords" checkbox driving two fields

    #312
  • renderToggle={(visible) => …} replaces the eye icon inside the kit's button, and hideToggle removes the button when another control owns the state

    #322
  • translations.visibilityTrigger(visible) localizes the reveal button's accessible name

    #312
  • The reveal toggle is in the Tab order and toggles on Enter, Space and screen-reader activation; toggleTabIndex={-1} restores the old pointer-only button

    #323
  • In development, a warning flags a renderToggle whose visible text is missing from the button's accessible name, and points to translations.visibilityTrigger

    #324
v0.6.0
  • Inputs drop the filled variant and the color prop

    #96
v0.5.1
  • Field's label names the real control through aria-labelledby

    #77
v0.5.0
  • Wire the field's description and error to the control with aria-describedby and aria-errormessage

    #57
v0.0.1
  • Textarea, PasswordInput, NumberInput and PinInput

On this page