# Input Password

A cross-platform React password input component with visibility toggle, validation checkmark, and clear functionality.
<!-- BEGIN:xui-mcp-instructions:input-password -->
A specialised text input for password entry. Extends the standard input with password-specific controls: a show/hide visibility toggle, an optional clear button, an optional check icon for password confirmation fields, and a right icons block that groups these controls. Always renders with type=*"password"* semantics (masked by default) unless the user explicitly toggles visibility.

### When to use

For any field where the user must enter a password — login, registration, account settings, password reset

For password confirmation fields (e.g. *"Confirm new password"*) — use alongside the primary password field

When the form requires the user to create a new password and strength feedback is shown nearby

### When not to use

- For non-sensitive text fields — use the standard Input component
- For PINs or short numeric codes — use a dedicated PIN input or a numeric Input with inputmode=*"numeric"*
- When the input must always remain masked with no reveal option (e.g. a hardware security key PIN) — handle with a custom component that omits the visibility toggle

### Content guidelines

Placeholder text — use Password or a format hint like At least 8 characters for create-password fields. Do not use verbose instructions as placeholder — they disappear when the user starts typing.

Field labels — always provide a visible label: *"Password"*, *"Current password"*, *"New password"*, *"Confirm new password"*. Do not rely on placeholder alone.
Error messages — be specific:
- *"Password is required"*
- *"Password must be at least 8 characters"*
- *"Password must contain at least one number and one special character"*
- *"Passwords do not match"* (on the confirm field)
- *"Incorrect password"* (on the login form after a failed attempt)
- Visibility toggle label — the toggle icon button must have an accessible label that reflects the current state: aria-label=*"Show password"* when masked, aria-label=*"Hide password"* when revealed.
- Avoid password rules in error messages only — communicate password requirements upfront (e.g. in a helper text or a requirements list below the field) so users know the rules before they make a mistake.

Behaviour guidelines (from industry practice)

Default masking — the field must always render in masked state (Visibility=False) on mount, even if a value is pre-filled. Never initialise with Visibility=True.

Visibility toggle — clicking the eye icon toggles between masked and plain text. The toggle must work with mouse, touch, and keyboard (Enter/Space when the icon has focus). After toggling, focus must remain in the input field, not on the icon button.

Remove button — show the remove button only when the field has a value (Filled=True). Hide it in the empty state. Clicking removes the value and returns focus to the input.

Check icon right — update in real time as the user types in either password field. Show when values match, hide when they diverge. Do not show the check icon if either field is empty.

Autocomplete — use autocomplete=*"current-password"* for login/current-password fields and autocomplete=*"new-password"* for create-password/confirm-password fields. This allows password managers to fill and save credentials correctly and satisfies WCAG 1.3.5.

Password managers — do not block paste (onpaste prevention) in password fields. Password managers and users rely on paste. Blocking paste is a security anti-pattern and degrades usability significantly.

Validation timing — validate on blur (when the user leaves the field) or on form submit. Do not validate every keystroke. For *"Confirm password"* fields, re-validate when either field changes.

Error state — switch to State=Error with a specific error message on blur or submit:
- If the password does not meet requirements, show which requirement failed
- If passwords do not match, show the error on the confirm field (not the primary)
- Strength indicator — password strength feedback (e.g. a progress bar or label) lives outside the InputPassword component. Place it between the primary and confirm password fields.
- Caps Lock warning — optionally surface a Caps Lock indicator when the user has Caps Lock active in a masked field. Show it as a helper text below the field or a tooltip on the visibility icon.
- Disabled state — use State=Disable for fields that cannot be edited in the current context (e.g. a federated account where the password is managed externally). Always show a reason nearby.

### Accessibility

The field must use type=*"password"* in masked state and type=*"text"* in revealed state. Never use type=*"text"* permanently for a password field.

Provide a visible label via <label for> or aria-labelledby. Do not use placeholder as the only label.

Use autocomplete=*"current-password"* or autocomplete=*"new-password"* as appropriate — required for WCAG 1.3.5 (Identify Input Purpose).

The visibility toggle icon button must have a dynamic aria-label reflecting current state: *"Show password"* or *"Hide password"*. It must also have aria-pressed (or aria-expanded) set appropriately, or use aria-label alone to communicate the current action.

When the field transitions to Visibility=True, announce the change to screen readers using aria-live=*"polite"* on a visually hidden region (e.g. *"Password is now visible"*).

The remove button (✕) must have aria-label=*"Clear password"*.

The check icon (✓) is status feedback — it must be wrapped in an aria-live=*"polite"* region that announces *"Passwords match"* when it appears and *"Passwords do not match"* when it disappears.

When State=Error, the error message must be associated via aria-describedby so screen readers announce it when the field receives focus.

When State=Disable, the field must have aria-disabled=*"true"*.

The visibility toggle must be keyboard-operable: Tab to focus, Enter/Space to activate.
<!-- END:xui-mcp-instructions:input-password -->

## Installation

```bash
npm install @xsolla/xui-input-password
```

## Demo

### Basic Password Input

```tsx
import * as React from "react";
import { InputPassword } from "@xsolla/xui-input-password";

export default function BasicPassword() {
  const [password, setPassword] = React.useState("");

  return (
    <InputPassword
      value={password}
      onChange={(e) => setPassword(e.target.value)}
      placeholder="Enter password"
    />
  );
}
```

### With Visibility Toggle

```tsx
import * as React from "react";
import { InputPassword } from "@xsolla/xui-input-password";

export default function VisibilityToggle() {
  const [password, setPassword] = React.useState("");

  return (
    <InputPassword
      value={password}
      onChange={(e) => setPassword(e.target.value)}
      extraSee={true}
      placeholder="Enter password"
    />
  );
}
```

### With Validation

```tsx
import * as React from "react";
import { InputPassword } from "@xsolla/xui-input-password";

export default function PasswordValidation() {
  const [password, setPassword] = React.useState("");

  return (
    <InputPassword
      value={password}
      onChange={(e) => setPassword(e.target.value)}
      extraSee={true}
      extraCheckup={(pass) => pass.length >= 8}
      label="Password"
      helperText="At least 8 characters"
      placeholder="Enter password"
    />
  );
}
```

### With Error State

```tsx
import * as React from "react";
import { InputPassword } from "@xsolla/xui-input-password";

export default function PasswordError() {
  const [password, setPassword] = React.useState("");

  return (
    <InputPassword
      value={password}
      onChange={(e) => setPassword(e.target.value)}
      extraSee={true}
      error={password.length > 0 && password.length < 8}
      errorMessage="Password must be at least 8 characters"
      label="Password"
      placeholder="Enter password"
    />
  );
}
```

## Anatomy

```jsx
import { InputPassword } from "@xsolla/xui-input-password";

<InputPassword
  value={password} // Controlled value
  onChange={handleChange} // Change handler (event)
  onChangeText={handleText} // Change handler (string)
  extraSee={true} // Show visibility toggle
  extraClear={true} // Show clear button
  extraCheckup={validateFn} // Validation function
  label="Label" // Label above input
  helperText="Help text" // Helper text below
  error={boolean} // Error state
  errorMessage="Error" // Error message
  size="md" // Size variant
  disabled={false} // Disabled state
/>;
```

## Examples

### Full Featured Password

```tsx
import * as React from "react";
import { InputPassword } from "@xsolla/xui-input-password";

export default function FullPassword() {
  const [password, setPassword] = React.useState("");
  const [error, setError] = React.useState("");

  const validatePassword = (pass: string) => {
    if (pass.length < 8) return false;
    if (!/[A-Z]/.test(pass)) return false;
    if (!/[0-9]/.test(pass)) return false;
    return true;
  };

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    setPassword(value);
    if (value && !validatePassword(value)) {
      setError("Password must be 8+ chars with uppercase and number");
    } else {
      setError("");
    }
  };

  return (
    <InputPassword
      value={password}
      onChange={handleChange}
      extraSee={true}
      extraClear={true}
      extraCheckup={validatePassword}
      onRemove={() => setPassword("")}
      label="Password"
      helperText="8+ characters, uppercase, and number required"
      error={!!error}
      errorMessage={error}
      placeholder="Create a strong password"
    />
  );
}
```

### Password Sizes

```tsx
import * as React from "react";
import { InputPassword } from "@xsolla/xui-input-password";

export default function PasswordSizes() {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      <InputPassword size="xs" placeholder="Extra Small" extraSee />
      <InputPassword size="sm" placeholder="Small" extraSee />
      <InputPassword size="md" placeholder="Medium" extraSee />
      <InputPassword size="lg" placeholder="Large" extraSee />
      <InputPassword size="xl" placeholder="Extra Large" extraSee />
    </div>
  );
}
```

## API Reference

### InputPassword

**InputPassword Props:**

| Prop           | Type                                   | Default | Description                                                                                                   |
| :------------- | :------------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------ |
| `testID`       | `string`                               | —       | Test ID for testing frameworks. On web this renders as `data-testid`; on React Native it renders as `testID`. |
| value          | `string`                               | -       | Controlled input value.                                                                                       |
| onChange       | `(e: ChangeEvent) => void`             | -       | Change event handler.                                                                                         |
| onChangeText   | `(text: string) => void`               | -       | Text change handler.                                                                                          |
| extraSee       | `boolean`                              | `false` | Show visibility toggle button.                                                                                |
| extraClear     | `boolean`                              | `false` | Show clear button.                                                                                            |
| extraCheckup   | `(pass: string) => boolean`            | -       | Validation function.                                                                                          |
| initialVisible | `boolean`                              | `false` | Initial password visibility.                                                                                  |
| size           | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"md"`  | Component size.                                                                                               |
| label          | `string`                               | -       | Label above input.                                                                                            |
| helperText     | `string`                               | -       | Helper text below input.                                                                                      |
| error          | `boolean`                              | `false` | Error state.                                                                                                  |
| errorMessage   | `string`                               | -       | Error message.                                                                                                |
| disabled       | `boolean`                              | `false` | Disabled state.                                                                                               |
| placeholder    | `string`                               | -       | Placeholder text.                                                                                             |
| name           | `string`                               | -       | Input name attribute.                                                                                         |
| onRemove       | `() => void`                           | -       | Clear button handler.                                                                                         |
| aria-label     | `string`                               | -       | Accessible label.                                                                                             |
| testID         | `string`                               | -       | Test identifier.                                                                                              |

## Behavior

- **Visibility Toggle**: The eye icon reflects the current state of the password:
  - Open eye = Password is currently **visible** (type="text")
  - Closed eye = Password is currently **hidden** (type="password")
  - This follows modern design system conventions (Material, Apple, Atlassian, Polaris)
- Checkmark appears when `extraCheckup` returns true
- Clear button appears when input has value and `extraClear` is true
- Error state shows red border and error message

## Accessibility

- Uses `type="password"` or `type="text"` based on visibility
- Visibility toggle button has appropriate `aria-label`:
  - "Show password" when password is hidden
  - "Hide password" when password is visible
- Toggle button uses `aria-pressed` to indicate current state
- Error messages linked via `aria-describedby` for screen reader context
- Labels properly linked via `aria-labelledby` when provided
- Error messages use `role="alert"` for immediate announcement
