Password Input
A password field with visibility toggle and live strength meter.
Installation
npx shadcn@latest add @hazine/password-inputSource
"use client"
import * as React from "react"
import { EyeIcon, EyeOffIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { Input } from "@/components/ui/input"
function strength(value: string) {
let score = 0
if (value.length >= 8) score++
if (value.length >= 12) score++
if (/[A-Z]/.test(value) && /[a-z]/.test(value)) score++
if (/\d/.test(value)) score++
if (/[^A-Za-z0-9]/.test(value)) score++
return Math.min(score, 4)
}
const labels = ["Too weak", "Weak", "Fair", "Good", "Strong"]
const colors = [
"bg-red-500",
"bg-red-500",
"bg-amber-500",
"bg-emerald-500",
"bg-emerald-500",
]
interface PasswordInputProps
extends Omit<React.ComponentProps<typeof Input>, "type"> {
showStrength?: boolean
}
export function PasswordInput({
showStrength = false,
className,
onChange,
...props
}: PasswordInputProps) {
const [visible, setVisible] = React.useState(false)
const [value, setValue] = React.useState("")
const score = strength(value)
return (
<div className="flex w-full flex-col gap-2">
<div className="relative">
<Input
type={visible ? "text" : "password"}
className={cn("pr-9", className)}
onChange={(event) => {
setValue(event.target.value)
onChange?.(event)
}}
{...props}
/>
<button
type="button"
aria-label={visible ? "Hide password" : "Show password"}
className="absolute inset-y-0 right-0 flex w-9 items-center justify-center text-muted-foreground hover:text-foreground"
onClick={() => setVisible((v) => !v)}
>
{visible ? (
<EyeOffIcon className="size-4" />
) : (
<EyeIcon className="size-4" />
)}
</button>
</div>
{showStrength && value.length > 0 && (
<div className="flex flex-col gap-1.5">
<div className="flex gap-1" aria-hidden="true">
{Array.from({ length: 4 }).map((_, i) => (
<div
key={i}
className={cn(
"h-1 flex-1 rounded-full bg-muted transition-colors",
i < score && colors[score]
)}
/>
))}
</div>
<p className="text-xs text-muted-foreground" role="status">
{labels[score]}
</p>
</div>
)}
</div>
)
}
Dependencies
- input
- lucide-react