<!-- Protocore Design System — PasswordInput -->
# PasswordInput

- **Category:** Inputs (`inputs`)
- **Slug:** `inputs/password-input`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { PasswordInput } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/inputs/password-input

> Masked text field with a trailing reveal/hide toggle and an optional strength-meter slot.

## When to use it

Use **PasswordInput** for any secret a user types — a login password, an API secret being set, a passphrase. The reveal toggle cuts typos on long secrets without weakening masking by default. For one-time codes use **OTPInput**; for a plain non-secret string use **Input**. Compute strength yourself and pass the result to `meter` — the component intentionally ships no scoring so you can plug in zxcvbn, a server rule, or nothing at all.

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `className` | `string` | no | — | — |
| `defaultValue` | `string` | no | — | Initial value when uncontrolled. |
| `defaultVisible` | `boolean` | no | `false` | Start with the value revealed (uncontrolled visibility). |
| `hideToggle` | `boolean` | no | `false` | Hide the reveal/hide toggle entirely (e.g. a confirm-password field). |
| `invalid` | `boolean` | no | — | Mark the field invalid — danger border plus `aria-invalid`. |
| `meter` | `ReactNode` | no | — | Optional strength-meter node rendered directly below the field. |
| `onValueChange` | `((value: string) => void)` | no | — | Fires with the new value on every edit. |
| `onVisibleChange` | `((visible: boolean) => void)` | no | — | Fires with the next visibility when the toggle is pressed. |
| `size` | `enum` | no | — | Control height: `sm` (32) · `md` (36, default) · `lg` (40). |
| `startAdornment` | `ReactNode` | no | — | Leading slot (muted; mono for glyph/unit labels), rendered inside the field. |
| `style` | `CSSProperties` | no | — | — |
| `toggleLabel` | `{ show: string; hide: string; }` | no | `{ show: "Show password", hide: "Hide password" }` | Accessible label for the reveal/hide toggle. |
| `value` | `string` | no | — | Controlled value. Pair with `onValueChange`. |
| `visible` | `boolean` | no | — | Controlled visibility — when set, the toggle calls `onVisibleChange` instead of owning state. |

## Examples

### Basics

The field starts masked. The trailing eye toggle swaps between `password` and `text`; its `aria-label` flips between show and hide, and focus stays in the field.

```tsx
import { useState } from "react";
import { PasswordInput, Field } from "@protocore/pds";

export default function Demo() {
  const [value, setValue] = useState("");

  return (
    <div style={{ width: 300 }}>
      <Field label="Vault passphrase" hint="Used to unseal the local key store.">
        <PasswordInput value={value} onValueChange={setValue} placeholder="Enter passphrase…" />
      </Field>
    </div>
  );
}
```

### Strength meter

Pass any node to `meter` to render a strength readout directly below the field — a ProgressBar, a row of segments, or a hint line. The meter is yours to compute.

```tsx
import { useState } from "react";
import { PasswordInput, ProgressBar, Text } from "@protocore/pds";

const LABELS = ["Too weak", "Weak", "Fair", "Strong", "Excellent"];

// Toy scorer — length + variety. Replace with zxcvbn or a server rule in production.
function score(pw: string): number {
  let s = 0;
  if (pw.length >= 8) s++;
  if (pw.length >= 12) s++;
  if (/[a-z]/.test(pw) && /[A-Z]/.test(pw)) s++;
  if (/\d/.test(pw) && /[^A-Za-z0-9]/.test(pw)) s++;
  return Math.min(s, 4);
}

export default function Demo() {
  const [value, setValue] = useState("Set!ll3ment");
  const s = value ? score(value) : 0;
  const tone = s <= 1 ? "danger" : s === 2 ? "warning" : "success";

  return (
    <div style={{ width: 300 }}>
      <PasswordInput
        aria-label="New password"
        value={value}
        onValueChange={setValue}
        placeholder="Choose a strong password…"
        meter={
          <div style={{ display: "grid", gap: 6 }}>
            <ProgressBar
              value={value ? ((s + 1) / 5) * 100 : 0}
              tone={tone}
              label="Password strength"
            />
            <Text size="sm" color="muted">
              {value ? LABELS[s] : "Start typing to rate strength"}
            </Text>
          </div>
        }
      />
    </div>
  );
}
```

## Do & don't

**Do**

- Give the field an accessible name via `aria-label` or a wrapping `Field`.
- Keep the default masked state — reveal is opt-in, per field.
- Use `meter` for feedback the user can act on, not a decorative bar.
- Set `hideToggle` on confirm fields so the reveal control isn't duplicated.

**Don't**

- Don't default to revealed — it removes shoulder-surfing protection.
- Don't reuse it for one-time codes; OTPInput handles those.
- Don't hard-code a strength algorithm into the field; feed `meter` instead.
- Don't remove the toggle on a primary password field.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Tab` | Move focus to the field, then to the reveal toggle. |
| `Enter / Space` | On the toggle, reveal or re-mask the value. |

**Notes**

- The toggle is a real `button` with an `aria-label` that flips between 'Show password' and 'Hide password' and an `aria-pressed` state.
- Pressing the toggle returns focus to the field so the caret position is preserved.
- The masked field composes the base `Input`, inheriting its focus ring, sizes, and invalid state.

## Related

`input`, `field`, `otp-input`, `number-input`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
