Skip to content

Instantly share code, notes, and snippets.

@mjbalcueva
Last active May 2, 2024 18:58
Show Gist options
  • Star 40 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • 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 { forwardRef, useState } from "react"
import { EyeIcon, EyeOffIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input, InputProps } from "@/components/ui/input"
import { cn } from "@/lib/utils"
const PasswordInput = forwardRef<HTMLInputElement, InputProps>(
({ className, ...props }, ref) => {
const [showPassword, setShowPassword] = 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
@mjbalcueva
Copy link
Author

and here's what it looks like:
darkmodesample
lightmodesample

@cblberlin
Copy link

gread example, thx

@lucassarcanjo
Copy link

awesome man, thanks!

@NirajD10
Copy link

NirajD10 commented Feb 24, 2024

for those people who are having trouble error message seen as "Function components cannot be given refs. Attempts to access this ref will fail.",
Use React.forwardRef instead of forwardRef

and make sure don't forget import
import React, {useState} from "react"

not
import { forwardRef, useState } from "react"

@findhridoy
Copy link

thanks!

@noflame
Copy link

noflame commented Mar 20, 2024

Love you, 💯

@EricTsai83
Copy link

thanks a lot.

@jsantanders
Copy link

Thanks!

@ProDanish203
Copy link

thanks alot

@laryhills
Copy link

Thanks a lot, made a shadcn-svelte version. -> https://gist.github.com/laryhills/dddd2d17dd0db9179f5686c6afbc9c94

@immdraselkhan
Copy link

immdraselkhan commented Apr 30, 2024

import { EyeIcon, EyeOffIcon } from "lucide-react";
import { useFormContext } from "react-hook-form";
import { Box } from "@/components/ui/box";
import {
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { createElement, useState } from "react";

type PasswordFieldProps = {
  description?: string | JSX.Element;
};

export function PasswordField({ description }: PasswordFieldProps) {
  const { control, getFieldState } = useFormContext();
  const [passwordVisibility, setPasswordVisibility] = useState(false);

  return (
    <FormField
      control={control}
      name="password"
      render={({ field }) => (
        <FormItem>
          <FormControl>
            <Box className="relative">
              <Input
                type={passwordVisibility ? "text" : "password"}
                placeholder="Enter password"
                autoComplete="on"
                {...field}
                className={`pr-12 ${getFieldState("password").error && "border-red-500"}`}
              />
              <Box
                className="absolute inset-y-0 right-0 flex cursor-pointer items-center p-3 text-muted-foreground"
                onClick={() => setPasswordVisibility(!passwordVisibility)}
              >
                {createElement(passwordVisibility ? EyeOffIcon : EyeIcon, {
                  className: "h-6 w-6",
                })}
              </Box>
            </Box>
          </FormControl>
          <FormMessage />
          {description && <FormDescription>{description}</FormDescription>}
        </FormItem>
      )}
    />
  );
}

This is another approach with better control. Make sure to wrap the form using FormProvider.

Usage example:

<PasswordField
  // description={<Link href="reset">Forgot your password?</Link>}
  description={"Forgot your password?"}
/>

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