codefast/ui

Command Palette

Search for a command to run...

Source
Form

Input Password

Password field with a show/hide toggle. Extends Input Group with no extra markup.

Examples

Show / hide toggle

A built-in eye button reveals the value — no extra markup needed.

Click the eye to reveal each field.

18 lines
import { InputPassword } from "@codefast/ui/input-password";
import { Label } from "@codefast/ui/label";

export function InputPasswordFields() {
  return (
    <div className="w-full max-w-xs space-y-3">
      <div className="grid gap-1.5">
        <Label htmlFor="pw-current">Current password</Label>
        <InputPassword id="pw-current" placeholder="••••••••" />
      </div>
      <div className="grid gap-1.5">
        <Label htmlFor="pw-new">New password</Label>
        <InputPassword id="pw-new" placeholder="Min. 8 characters" />
      </div>
      <p className="text-xs text-ui-muted">Click the eye to reveal each field.</p>
    </div>
  );
}

Live strength meter

Drive a strength bar from the controlled value as the user types.

Enter a password

59 lines
import { InputPassword } from "@codefast/ui/input-password";
import { Label } from "@codefast/ui/label";
import { cn } from "@codefast/ui/lib/utils";
import type { ChangeEvent } from "react";
import { useState } from "react";

const LEVELS = [
  { label: "Too weak", color: "bg-rose-500" },
  { label: "Weak", color: "bg-amber-500" },
  { label: "Fair", color: "bg-yellow-500" },
  { label: "Strong", color: "bg-emerald-500" },
];

function score(value: string): number {
  let points = 0;
  if (value.length >= 8) {
    points += 1;
  }
  if (/[A-Z]/.test(value) && /[a-z]/.test(value)) {
    points += 1;
  }
  if (/\d/.test(value)) {
    points += 1;
  }
  if (/[^A-Za-z0-9]/.test(value)) {
    points += 1;
  }
  return Math.min(points, 4);
}

export function InputPasswordStrength() {
  const [value, setValue] = useState("");
  const points = score(value);
  const level = points > 0 ? LEVELS[points - 1] : undefined;

  return (
    <div className="grid w-full max-w-xs gap-1.5">
      <Label htmlFor="pw-strength">New password</Label>
      <InputPassword
        id="pw-strength"
        placeholder="Type to test strength"
        value={value}
        onChange={(event: ChangeEvent<HTMLInputElement>) => setValue(event.target.value)}
      />
      <div className="mt-1 flex gap-1">
        {LEVELS.map((_, index) => (
          <span
            key={index}
            className={cn(
              "h-1 flex-1 rounded-full transition-colors",
              index < points ? (level?.color ?? "bg-ui-border") : "bg-ui-border",
            )}
          />
        ))}
      </div>
      <p className="text-xs text-ui-muted">{level ? level.label : "Enter a password"}</p>
    </div>
  );
}

Confirm match

Two fields with a live match check.

37 lines
import { InputPassword } from "@codefast/ui/input-password";
import { Label } from "@codefast/ui/label";
import { useState } from "react";

export function InputPasswordConfirm() {
  const [password, setPassword] = useState("");
  const [confirm, setConfirm] = useState("");

  const mismatch = confirm.length > 0 && password !== confirm;

  return (
    <div className="w-full max-w-xs space-y-3">
      <div className="grid gap-1.5">
        <Label htmlFor="pw-confirm-new">New password</Label>
        <InputPassword
          id="pw-confirm-new"
          value={password}
          onChange={(event) => {
            setPassword(event.target.value);
          }}
        />
      </div>
      <div className="grid gap-1.5">
        <Label htmlFor="pw-confirm-repeat">Confirm password</Label>
        <InputPassword
          id="pw-confirm-repeat"
          value={confirm}
          aria-invalid={mismatch || undefined}
          onChange={(event) => {
            setConfirm(event.target.value);
          }}
        />
        {mismatch ? <p className="text-xs text-rose-500">Passwords don’t match.</p> : null}
      </div>
    </div>
  );
}

Usage

The minimal import and composition — see Examples below for styled, real-world variants.

5 lines
import { InputPassword } from "@codefast/ui/input-password";

export function InputPasswordUsage() {
  return <InputPassword placeholder="••••••••" />;
}

Anatomy

How the parts nest — every slot the component exposes, in composition order.

InputPassword

Features

  • Built-in show/hide toggle — internally an InputGroupInput plus an InputGroupButton, no extra markup needed.
  • The toggle’s aria-label swaps between "Show password" and "Hide password" as it’s clicked; it does not set aria-pressed.
  • Forwards every native input prop except type, which the component manages internally.

API reference

Props for each part of the component. All native element props are also forwarded.

InputPassword

Extends Input with a reveal toggle. Forwards all native input props.

valuestring

The controlled value.

onChangeReact.ChangeEventHandler<HTMLInputElement>

Called when the value changes.

disabledboolean

Disables the field and the toggle.

Defaultfalse

Accessibility

Built to be keyboard-navigable and screen-reader friendly out of the box.

  • The reveal button relabels itself ("Show password" / "Hide password") as it toggles — that changing label, not aria-pressed, is what gets announced.
  • Set autoComplete (current-password / new-password) to help password managers.
  • Associate a Label via htmlFor / id.

Guidelines

Conventions that keep usage consistent across an app.

Do

  • Offer the reveal toggle so users can check what they typed.
  • Show password requirements and live feedback on sign-up.

Don’t

  • Don’t block paste — it breaks password managers.
  • Don’t impose arbitrary maximum lengths.

Explore further

Ready to integrate?

Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.