# Password Strength Bar

Password Strength Bar [#password-strength-bar]

The **Password Strength Bar** renders a themed horizontal bar and label that
visualize how strong a password or passphrase is. It is **presentational only**:
your app computes the strength (`0`–`100`), the label string, and a semantic
color token, then passes them in — pairing with [`deriveEncryptionKey`](https://localmode.dev/docs/core) /
Web Crypto from `@localmode/core` in the surrounding key-derivation flow.

> **No turnkey hook.** This primitive does no strength estimation. There is no
> `@localmode/react` hook behind it — the app supplies the computed `value`,
> `label`, and `color` (typically from length/entropy or a zxcvbn-style
> estimator). The component only renders the state you give it.

**When to use it:** any password-creation, passphrase, or key-derivation form
where you already estimate strength and want a themed meter to surface it.

Preview [#preview]

```tsx
'use client';

import { useState } from 'react';
import {
  PasswordStrengthBar,
  type StrengthColor,
} from '@/components/password-strength-bar';

/**
 * Demo for the PasswordStrengthBar component, used by the docs live preview.
 *
 * Computes a trivial strength score from the typed value purely to drive the
 * bar — in a real app this is your password policy / entropy estimator paired
 * with `@localmode/core` `deriveKey`. The component itself only renders state.
 */
export default function PasswordStrengthBarDemo() {
  // Seed with a sample password so the preview lands on a populated state
  // (colored fill + label) instead of an empty bar. This is mock UI state —
  // no model fetch — the user can clear/retype to see other strengths.
  const [password, setPassword] = useState('Tr0ub4dour&3');

  // Demo-only heuristic: longer + mixed character classes → higher score.
  // Replace with your real estimator (length, entropy, zxcvbn) in production.
  const classes =
    (/[a-z]/.test(password) ? 1 : 0) +
    (/[A-Z]/.test(password) ? 1 : 0) +
    (/[0-9]/.test(password) ? 1 : 0) +
    (/[^a-zA-Z0-9]/.test(password) ? 1 : 0);
  const lengthScore = Math.min(password.length, 16) / 16; // 0..1
  const value = Math.round(lengthScore * 60 + (classes / 4) * 40);

  const color: StrengthColor =
    value < 40 ? 'error' : value < 70 ? 'warning' : 'success';
  const label =
    password.length === 0
      ? undefined
      : value < 40
        ? 'Weak'
        : value < 70
          ? 'Good'
          : 'Strong';

  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Type a password…"
        className="h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
      />
      <PasswordStrengthBar value={value} label={label} color={color} />
    </div>
  );
}
```

Installation [#installation]

```bash
npx shadcn@latest add @localmode/ui/security-privacy/password-strength-bar
```

Dependencies [#dependencies]

* **Data source:** purely presentational — it renders the `value` (0–100), `label`, and `color` you compute (from length/entropy or a zxcvbn-style estimator) and works anywhere; there is no producer hook. Pairs naturally with a `@localmode/core` `deriveEncryptionKey` / Web Crypto key-derivation flow, but that is optional.
* `clsx` + `tailwind-merge` — via the shared `cn()` util (installed automatically as a registry dependency)

Files installed [#files-installed]

* `password-strength-bar.tsx` — the component
* `lib/utils.ts` — the `cn()` helper (if not already present)

Props [#props]

**PasswordStrengthBar**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `number` | — | **Required.** Strength score, `0`–`100`. **The caller computes this** (length / entropy / a zxcvbn-style estimator) — the bar only renders it. Values outside the range are clamped. Pairs with `@localmode/core` `deriveKey`/crypto in the consuming key-derivation flow. |
| `label` | `string` | — | Human-readable strength label shown beside the bar (e.g. `"Weak"`, `"Good"`, `"Strong"`). The caller decides the wording; the component does not derive it from `value`. |
| `color` | `"success" \| "warning" \| "error"` | `"warning"` | Semantic color token for the fill and label. `error` (red) for weak, `warning` (amber) for medium, `success` (green) for strong. The caller picks the token from its own thresholds. |
| `hideLabel` | `boolean` | `false` | When true, hide the textual label and render the bar only. |

Examples [#examples]

Caller computes strength [#caller-computes-strength]

The app owns the estimator. Map your score onto a `0`–`100` value, a label, and a
semantic color token:

```tsx
import { PasswordStrengthBar, type StrengthColor } from '@/components/password-strength-bar';

function estimate(password: string) {
  const classes =
    (/[a-z]/.test(password) ? 1 : 0) +
    (/[A-Z]/.test(password) ? 1 : 0) +
    (/[0-9]/.test(password) ? 1 : 0) +
    (/[^a-zA-Z0-9]/.test(password) ? 1 : 0);
  const lengthScore = Math.min(password.length, 16) / 16;
  return Math.round(lengthScore * 60 + (classes / 4) * 40);
}

export function PasswordField({ password }: { password: string }) {
  const value = estimate(password);
  const color: StrengthColor = value < 40 ? 'error' : value < 70 ? 'warning' : 'success';
  const label = value < 40 ? 'Weak' : value < 70 ? 'Good' : 'Strong';
  return <PasswordStrengthBar value={value} label={label} color={color} />;
}
```

Pair with key derivation [#pair-with-key-derivation]

Estimate strength before deriving an encryption key with `@localmode/core`:

```tsx
import { deriveEncryptionKey } from '@localmode/core';
import { PasswordStrengthBar } from '@/components/password-strength-bar';

// 1. Surface strength as the user types
<PasswordStrengthBar value={strength} label={label} color={color} />

// 2. On submit, derive the key (Web Crypto, never leaves the device)
const { key } = await deriveEncryptionKey(password, salt);
```

Bar only (no label) [#bar-only-no-label]

```tsx
<PasswordStrengthBar value={72} color="success" hideLabel />
```

Customization [#customization]

The bar is styled entirely with shadcn/ui CSS-variable utilities (`bg-muted` for
the track) so it inherits your theme. The semantic fill/label colors use
Tailwind's `red` / `amber` / `emerald` palettes directly — swap those classes in
the copied `password-strength-bar.tsx` to match your design system, or wire them
to your own tokens.

Because you own the file, you can also change how `color` maps to tokens, or add
more granular labels (`Very weak`, `Fair`, …) — the component never derives the
label from `value`, so your thresholds stay in your code.