Inputs

Password Input

A styled password field with a show/hide toggle and an optional heuristic strength meter.

Preview in your theme

Loading preview…

"use client"

import * as React from "react"
import { Eye, EyeOff } from "lucide-react"
import { cn } from "@/lib/utils"

export interface PasswordInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type"> {
  /** Four-segment heuristic strength bar (length / mixed case / digit / symbol). Off by default; a hint, not a substitute for real validation — that belongs in zod. */
  showStrength?: boolean
}

/** Scores 0-4: one point each for length ≥ 8, mixed case, a digit and a symbol. A heuristic, not a security check. */
function scorePassword(value: string): number {
  let score = 0

Installation

npx shadcn@latest add https://ui.zyeon.ai/r/password-input.json

Prompt

The prompt behind this component — paste it into your AI assistant to recreate or adapt it.

Build a React + TypeScript + Tailwind "PasswordInput" component (lucide-react Eye/EyeOff).

Contract
- Export a forwardRef component whose ref points at the inner <input>;
  props extend InputHTMLAttributes<HTMLInputElement> minus "type" (the
  component owns type, switching it between "password" and "text").
- Add showStrength?: boolean (default false) — renders a 4-segment
  heuristic strength meter below the field.
- Fully supports both controlled (value + onChange) and uncontrolled
  (defaultValue) usage — value/defaultValue/onChange pass straight
  through to the inner input, unmodified in shape.

Behavior
- A ghost icon button on the right toggles the input's type between
  "password" and "text" (Eye when hidden, EyeOff when visible); type="button"
  so it never submits a form, aria-label switches between "Show password"
  and "Hide password", and it stays in the natural tab order.
- Strength scoring is 0-4, one point each for: length >= 8, mixed upper
  + lower case, at least one digit, at least one symbol. It runs against
  whatever the current value is — read straight from the value prop when
  controlled, or mirrored into internal state on every onChange when
  uncontrolled (this mirror never changes controlled/uncontrolled
  behavior, it only gives the meter something to score).
- Score renders as 4 small segments + one label: 0-1 "Weak", 2 "Fair",
  3 "Good", 4 "Strong". This is a heuristic hint only — real validation
  belongs in a zod schema on the form, not in this component.
- disabled dims the field and disables both the input and the toggle button.

Rendering & styling
- Outer field mimics the shadcn Input: flex h-9 items-center rounded-md
  border border-input bg-transparent px-3, focus-within:border-ring +
  focus-within:ring-ring/50 so the whole box focuses as one field; the
  inner <input> itself is borderless and bg-transparent.
- Strength segments: unfilled bg-muted; filled color depends on the score
  bucket via inline style — var(--destructive) for 1-2, var(--chart-4)
  for 3, var(--chart-2) for 4. Semantic tokens only, no hardcoded hex/oklch.
- Toggle button: ghost icon button (hover:bg-muted, focus-visible ring);
  merge the consumer className via cn() onto the field container.

Customization levers
- Strength rule set: swap scorePassword() for a zxcvbn call (or add more
  rules) — it only needs to keep returning 0-4 for the existing 4-segment
  UI.
- Segment count / labels: change the segment array length and
  STRENGTH_LABEL together for a different granularity.
- react-hook-form: wrap in a Controller (or spread {...field}) — the
  component's value/onChange contract is already a plain controlled
  input, no extra adapter needed; pair it with a zod schema
  (e.g. z.string().min(8)) for the actual pass/fail validation instead
  of relying on the UI meter.

Concepts

  • Controlled/uncontrolled mirroring — when a value is supplied it's the only source of truth; otherwise an internal shadow state tracks each keystroke purely so the strength meter has something to read.
  • Heuristic strength scoring — four independent rules (length, case mix, digit, symbol) each contribute one point; it's a fast UX signal, not a security guarantee.
  • Progressive disclosure via visibility toggle — the field defaults to masked and only reveals plaintext on an explicit, reversible user action.
  • Decoupled validation — the strength meter never blocks submission; real pass/fail rules belong in the form's zod schema, not in this component.

On This Page