Skip to content

Instantly share code, notes, and snippets.

@mjbalcueva
Last active May 6, 2025 01:20
Show Gist options
  • Save mjbalcueva/b21f39a8787e558d4c536bf68e267398 to your computer and use it in GitHub Desktop.
Save mjbalcueva/b21f39a8787e558d4c536bf68e267398 to your computer and use it in GitHub Desktop.
shadcn ui custom password input
'use client'
import * as React from 'react'
import { EyeIcon, EyeOffIcon } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input, type InputProps } from '@/components/ui/input'
import { cn } from '@/lib/utils'
const PasswordInput = React.forwardRef<HTMLInputElement, InputProps>(({ className, ...props }, ref) => {
const [showPassword, setShowPassword] = React.useState(false)
const disabled = props.value === '' || props.value === undefined || props.disabled
return (
<div className="relative">
<Input
type={showPassword ? 'text' : 'password'}
className={cn('hide-password-toggle pr-10', className)}
ref={ref}
{...props}
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowPassword((prev) => !prev)}
disabled={disabled}
>
{showPassword && !disabled ? (
<EyeIcon className="h-4 w-4" aria-hidden="true" />
) : (
<EyeOffIcon className="h-4 w-4" aria-hidden="true" />
)}
<span className="sr-only">{showPassword ? 'Hide password' : 'Show password'}</span>
</Button>
{/* hides browsers password toggles */}
<style>{`
.hide-password-toggle::-ms-reveal,
.hide-password-toggle::-ms-clear {
visibility: hidden;
pointer-events: none;
display: none;
}
`}</style>
</div>
)
})
PasswordInput.displayName = 'PasswordInput'
export { PasswordInput }
"use client"
import { useState } from "react"
import { PasswordInput } from "@/components/password-input"
import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
const SampleUseCase = () => {
const [currentPassword, setCurrentPassword] = useState("")
const [password, setPassword] = useState("")
const [passwordConfirmation, setPasswordConfirmation] = useState("")
return (
<div className="space-y-4">
<div>
<Label htmlFor="current_password">Current Password</Label>
<PasswordInput
id="current_password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
autoComplete="current-password"
/>
</div>
<div>
<Label htmlFor="password">New Password</Label>
<PasswordInput
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
autoComplete="new-password"
/>
</div>
<div>
<Label htmlFor="password_confirmation">Confirm Password</Label>
<PasswordInput
id="password_confirmation"
value={passwordConfirmation}
onChange={(e) => setPasswordConfirmation(e.target.value)}
autoComplete="new-password"
/>
</div>
<Button type="submit">Save</Button>
</div>
)
}
export default SampleUseCase
@joshpachner
Copy link

Thank you. I'll name my firstborn after you.
You are what gives me hope in humanity.

@IsoardiMarius
Copy link

thanks

@milinddhamu
Copy link

Shadcn components supposed to support ssr , so converting it directly to Client component doesnt make sense. I hope there's any better way to toggle.

@abustamam
Copy link

Shadcn components supposed to support ssr , so converting it directly to Client component doesnt make sense. I hope there's any better way to toggle.

Server components don't support hooks. So any UI, shad or otherwise, that requires interactivity, needs the use client directive.

https://react.dev/reference/rsc/server-components#adding-interactivity-to-server-components

@luislobo9b
Copy link

My Input component doesn't have InputProps, so I made this small change:

Before:

import { Input, type InputProps } from '@/components/ui/input'
const PasswordInput = React.forwardRef<HTMLInputElement, InputProps>(({ className, ...props }, ref) => {

After:

import { Input } from '@/components/ui/input'
const PasswordInput = React.forwardRef<HTMLInputElement, React.ComponentProps<typeof Input>>(({ className, ...props }, ref) => {

In other words, I simply replaced InputProps with React.ComponentProps<typeof Input>.

@abustamam
Copy link

My Input component doesn't have InputProps, so I made this small change:

Before:

import { Input, type InputProps } from '@/components/ui/input'
const PasswordInput = React.forwardRef<HTMLInputElement, InputProps>(({ className, ...props }, ref) => {

After:

import { Input } from '@/components/ui/input'
const PasswordInput = React.forwardRef<HTMLInputElement, React.ComponentProps<typeof Input>>(({ className, ...props }, ref) => {

In other words, I simply replaced InputProps with React.ComponentProps<typeof Input>.

That works; but you can also just export InputProps from the input file like @fillsanches recommended here

export interface InputProps
  extends React.InputHTMLAttributes<HTMLInputElement> {}

@luislobo9b
Copy link

@abustamam
Got it. It's better this way because if I want to add a customization, it will reflect on everyone using the Input, centralizing the typing. Thanks.

@fransachmadhw
Copy link

thank you, it works perfectly

@MatiasGOrtega
Copy link

Based on your ideas, I'm sharing my results with you. Sorry for the language in the code; I speak Spanish.
https://codesandbox.io/p/devbox/pruebas-con-tailwindcss-ngjr23

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment