A dark sign-in screen with a small logo, a welcome heading and a single card holding the form. The card has an email field, a password field with an eye button that reveals and hides what was typed, a remember me checkbox, the main sign-in button and a second button for single sign-on. A link to create an account sits below. The column is narrow, so it looks the same on a phone as on a desktop.
import { useState } from "react";
const field = "block h-11 w-full rounded-lg border border-slate-700 bg-slate-950 px-3.5 text-sm text-white placeholder:text-slate-500 focus:border-violet-400 focus:outline-none focus:ring-2 focus:ring-violet-400/30";
export default function SignInFormWithShowPasswordToggle() {
const [email, setEmail] = useState("[email protected]");
const [password, setPassword] = useState("sample-password");
const [visible, setVisible] = useState(false);
return (
<section className="bg-slate-950 px-4 py-14 text-white sm:py-20">
<div className="mx-auto w-full max-w-sm">
<div className="flex items-center gap-2.5">
<span className="flex h-9 w-9 items-center justify-center rounded-lg bg-violet-600">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="h-5 w-5">
<path d="M5 18V8l7-3 7 3v10M9 18v-6h6v6" />
</svg>
</span>
<span className="text-sm font-semibold tracking-tight">Larkspur</span>
</div>
<h2 className="mt-8 text-2xl font-semibold tracking-tight">Welcome back</h2>
<p className="mt-2 text-sm text-slate-400">Sign in to pick up where your team left off.</p>
<form action="#" onSubmit={(e) => e.preventDefault()} className="mt-8 rounded-2xl border border-slate-800 bg-slate-900 p-6">
<label htmlFor="signin-email" className="text-sm font-medium text-slate-200">Email</label>
<input id="signin-email" name="email" type="email" required autoComplete="email" value={email} onChange={(e) => setEmail(e.target.value)} className={`${field} mt-2`} />
<div className="mt-5 flex items-baseline justify-between">
<label htmlFor="signin-password" className="text-sm font-medium text-slate-200">Password</label>
<a href="#" className="rounded text-sm font-medium text-violet-300 hover:text-violet-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-400">Forgot it?</a>
</div>
<div className="relative mt-2">
<input
id="signin-password"
name="password"
type={visible ? "text" : "password"}
required
autoComplete="current-password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className={`${field} pr-12`}
/>
<button
type="button"
aria-label="Show password"
aria-pressed={visible}
onClick={() => setVisible(!visible)}
className="absolute inset-y-0 right-0 flex w-11 items-center justify-center rounded-r-lg text-slate-400 transition hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-400"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="h-5 w-5">
<path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z" />
<circle cx="12" cy="12" r="2.75" />
{visible ? null : <path d="m4 4 16 16" />}
</svg>
</button>
</div>
<div className="mt-5 flex min-h-[40px] items-center gap-3">
<input id="signin-remember" name="remember" type="checkbox" defaultChecked className="h-4 w-4 rounded border-slate-600 bg-slate-950 accent-violet-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900" />
<label htmlFor="signin-remember" className="text-sm text-slate-300">Keep me signed in on this device</label>
</div>
<button type="submit" className="mt-5 flex h-11 w-full items-center justify-center rounded-lg bg-violet-600 text-sm font-semibold text-white transition hover:bg-violet-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900">
Sign in
</button>
<div className="my-6 flex items-center gap-4 text-xs text-slate-400">
<span className="h-px flex-1 bg-slate-800" />
or
<span className="h-px flex-1 bg-slate-800" />
</div>
<button type="button" className="flex h-11 w-full items-center justify-center gap-2.5 rounded-lg border border-slate-700 text-sm font-semibold text-white transition hover:bg-slate-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="h-5 w-5 text-slate-300">
<circle cx="8" cy="14" r="4" />
<path d="m11 11 8-8m-3 3 2.5 2.500M13.5 8.500l2 2" />
</svg>
Use single sign-on
</button>
</form>
<p className="mt-6 text-center text-sm text-slate-400">
New to Larkspur?{" "}
<a href="#" className="rounded font-medium text-violet-300 hover:text-violet-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-400">Create an account</a>
</p>
</div>
</section>
);
}
Install with one command: npx shadcn@latest add https://we.inc/r/sign-in-form-with-show-password-toggle.json
A `visible` state switches the input `type` between `password` and `text`. The button flips that state, and the slash through the eye icon is drawn only while the password is hidden.
Yes, and it is widely recommended. It reduces failed attempts and lets people use longer passwords. The field starts hidden, so nothing is shown unless the person asks for it.
Replace the `onSubmit` handler with a call to your auth provider using the `email` and `password` state. Show any error under the password field in a paragraph with `role="alert"`.
Copy the outlined single sign-on button for each provider and place the copies under the divider. Keep the password button as the only filled button so the main action stays obvious.
Related: Multi-Step Signup Form with Step Indicator, Contact Form with Topic Select and Details Panel, Settings Panel with Toggle Switches and Save Bar, Button Set: Primary, Secondary, Ghost, Icon and Loading.
More forms or all free UI components. Start for free to have a full site built around it.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.