{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "password-input",
  "title": "Password Input",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A password field with a visibility toggle and a four-step strength guide that explains how to improve the entry.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/i18n.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/password-input.tsx",
      "content": "// Ballmac UI: Password Input. https://ui.ballmac.com/components/password-input\n\"use client\"\n\nimport * as React from \"react\"\nimport { Eye, EyeOff } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\nimport { useMessages } from \"@/lib/ballmac/i18n\"\n\ntype PasswordInputProps = Omit<\n  React.ComponentProps<\"input\">,\n  \"type\" | \"value\" | \"defaultValue\" | \"onChange\"\n> & {\n  /** Controlled password value. */\n  value?: string\n  /** Initial password when uncontrolled. */\n  defaultValue?: string\n  /** Called with the new password. */\n  onValueChange?: (value: string) => void\n  /** Show a four-step strength guide. */\n  showStrength?: boolean\n  /** Accessible name of the password field. */\n  label?: string\n}\nfunction PasswordInput({\n  value,\n  defaultValue = \"\",\n  onValueChange,\n  showStrength = true,\n  label,\n  className,\n  disabled,\n  ...props\n}: PasswordInputProps) {\n  const msg = useMessages()\n  label ??= msg(\"password-input.label\", \"Password\")\n  const [internal, setInternal] = React.useState(defaultValue)\n  const [visible, setVisible] = React.useState(false)\n  const current = value ?? internal\n  const score =\n    Number(current.length >= 8) +\n    Number(/[a-z]/.test(current) && /[A-Z]/.test(current)) +\n    Number(/\\d/.test(current)) +\n    Number(/[^a-zA-Z\\d]/.test(current))\n  const strength = [msg(\"password-input.tooShort\", \"Too short\"), msg(\"password-input.needsWork\", \"Needs work\"), msg(\"password-input.fair\", \"Fair\"), msg(\"password-input.good\", \"Good\"), msg(\"password-input.strong\", \"Strong\")][score]\n  function commit(next: string) {\n    if (value === undefined) setInternal(next)\n    onValueChange?.(next)\n  }\n  return (\n    <div data-slot=\"password-input\" className={cn(\"w-full min-w-0\", className)}>\n      <div className=\"flex h-9 items-center rounded-md border border-input bg-background shadow-xs transition-[border-color,box-shadow] duration-150 motion-reduce:transition-none focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50\">\n        <input\n          data-slot=\"password-input-field\"\n          type={visible ? \"text\" : \"password\"}\n          autoComplete=\"new-password\"\n          aria-label={label}\n          disabled={disabled}\n          value={current}\n          onChange={(event) => commit(event.currentTarget.value)}\n          className=\"h-full min-w-0 flex-1 bg-transparent px-3 text-sm text-foreground outline-none placeholder:text-muted-foreground disabled:opacity-50\"\n          {...props}\n        />\n        <button\n          type=\"button\"\n          aria-label={visible ? msg(\"password-input.hidePassword\", \"Hide password\") : msg(\"password-input.showPassword\", \"Show password\")}\n          aria-pressed={visible}\n          disabled={disabled}\n          onClick={() => setVisible(!visible)}\n          className=\"me-1 flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground outline-none hover:bg-accent hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-50\"\n        >\n          {visible ? (\n            <EyeOff aria-hidden=\"true\" className=\"size-4\" />\n          ) : (\n            <Eye aria-hidden=\"true\" className=\"size-4\" />\n          )}\n        </button>\n      </div>\n      {showStrength && current.length > 0 && (\n        <div data-slot=\"password-strength\" className=\"mt-2\" aria-live=\"polite\">\n          <div aria-hidden=\"true\" className=\"grid grid-cols-4 gap-1\">\n            {[1, 2, 3, 4].map((level) => (\n              <span\n                key={level}\n                className={cn(\n                  \"h-1 rounded-full bg-muted\",\n                  score >= level &&\n                    (score < 3 ? \"bg-destructive\" : \"bg-chart-2\"),\n                )}\n              />\n            ))}\n          </div>\n          <p className=\"mt-1.5 text-xs text-muted-foreground\">\n            {msg(\"password-input.strengthHint\", \"Strength: {strength}. Use 8+ characters, mixed case, a number, and a symbol.\", { strength })}\n          </p>\n        </div>\n      )}\n    </div>\n  )\n}\nexport { PasswordInput, type PasswordInputProps }\n",
      "type": "registry:ui",
      "target": "@components/ballmac/password-input.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "password",
      "security",
      "strength"
    ],
    "version": "1.0.0",
    "updated": "2026-09-30",
    "ai": {
      "summary": "A password field with a visibility toggle and a four-step strength guide that explains how to improve the entry.",
      "whenToUse": [
        "Create or change a password",
        "Give immediate guidance while typing"
      ],
      "whenNotToUse": [
        "Use input for a standard text field"
      ],
      "composesWith": [
        "input"
      ],
      "a11y": [
        {
          "keys": "Tab / Space",
          "action": "Toggles password visibility"
        }
      ],
      "customization": [
        "showStrength",
        "value / defaultValue and onValueChange"
      ]
    },
    "examples": [
      "password-input-demo",
      "password-input-states"
    ],
    "url": "https://ui.ballmac.com/components/password-input"
  },
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}