# Login Account

> A two-step account sign-in form that asks for an email, then reveals a password field.

A shadcn/ui block from the [@esuiss](https://suiss.app/ui?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/esuiss/login-account).

- Page, with a live preview: https://shadcn.exchange/r/esuiss/login-account
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/esuiss/login-account-light-e9ca1525372e43a2.png
- Registry item: https://suiss.app/ui/r/new-york-v4/login-account.json
- Categories: blocks, login
- Listed: 2026-10-20, source last changed 2026-10-20

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/esuiss/login-account.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@esuiss/avatar`, `@esuiss/button`, `@esuiss/checkbox`, `@esuiss/input`, `@esuiss/label`, `@esuiss/spinner`

## Files

### `components/blocks/login-account.tsx`

```tsx
"use client"

import * as React from "react"

import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Spinner } from "@/components/ui/spinner"

const EMAIL = /.+@.+\..+/

const SHAKE = [0, -10, 9, -6, 4, 0].map((x) => ({
  transform: `translateX(${x}px)`,
}))

function SubmitArrow({ ready, busy }: { ready: boolean; busy: boolean }) {
  return (
    <Button
      type="submit"
      variant="outline"
      size="icon-sm"
      aria-label="Continue"
      disabled={!ready || busy}
      className="me-2.5 size-7.5 border-[1.5px] border-muted-foreground bg-transparent text-muted-foreground dark:bg-transparent disabled:opacity-50"
    >
      {busy ? (
        <Spinner />
      ) : (
        <IconPlaceholder
          lucide="ArrowRightIcon"
          tabler="IconArrowRight"
          hugeicons="ArrowRight02Icon"
          phosphor="ArrowRightIcon"
          remixicon="RiArrowRightLine"
          className="size-3.5 rtl:rotate-180"
        />
      )}
    </Button>
  )
}

export function LoginAccount() {
  const idPrefix = React.useId()
  const [step, setStep] = React.useState<"email" | "password">("email")
  const [email, setEmail] = React.useState("")
  const [password, setPassword] = React.useState("")
  const [error, setError] = React.useState("")
  const [busy, setBusy] = React.useState(false)
  const [signedIn, setSignedIn] = React.useState(false)
  const fieldsRef = React.useRef<HTMLDivElement>(null)
  const passwordRef = React.useRef<HTMLInputElement>(null)

  const fail = (message: string) => {
    setError(message)
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return
    fieldsRef.current?.animate(SHAKE, { duration: 420, easing: "ease-out" })
  }

  const submit = (event: React.FormEvent) => {
    event.preventDefault()
    if (step === "email") {
      if (!EMAIL.test(email)) return fail("Enter a valid suiss Account.")
      setError("")
      setStep("password")
      window.setTimeout(() => passwordRef.current?.focus(), 380)
      return
    }
    if (password.length < 4)
      return fail("Your suiss Account or password was incorrect.")
    setError("")
    setBusy(true)
    window.setTimeout(() => {
      setBusy(false)
      setSignedIn(true)
    }, 1200)
  }

  if (signedIn) {
    return (
      <section className="flex min-h-150 items-center justify-center p-6">
        <div className="flex flex-col items-center gap-3.5 text-center transition-[opacity,translate] duration-1000 ease-[cubic-bezier(0.32,0.72,0,1)] starting:translate-y-4.5 starting:opacity-0 motion-reduce:transition-none">
          <Avatar className="size-20">
            <AvatarFallback className="bg-none bg-muted text-3xl text-foreground">
              {email[0]}
            </AvatarFallback>
          </Avatar>
          <h1 className="text-5xl font-semibold tracking-tight">Hello.</h1>
          <p className="text-lg text-muted-foreground">{email}</p>
          <button
            type="button"
            onClick={() => {
              setSignedIn(false)
              setStep("email")
              setPassword("")
            }}
            className="rounded-xs text-lg text-primary outline-none hover:underline focus-visible:ring-3 focus-visible:ring-ring/50"
          >
            Sign out ›
          </button>
        </div>
      </section>
    )
  }

  return (
    <section className="flex min-h-150 items-center justify-center p-6">
      <form
        noValidate
        onSubmit={submit}
        className="flex w-115 max-w-full flex-col items-center text-center"
      >
        <h1 className="mb-8 text-4xl font-semibold tracking-tight text-balance md:text-5xl">
          Sign in with your suiss Account
        </h1>
        <div
          ref={fieldsRef}
          data-invalid={error ? "" : undefined}
          className="w-full overflow-hidden rounded-xl border border-input bg-background text-start transition-[border-color,background-color] duration-200 has-focus-visible:border-ring has-focus-visible:ring-3 has-focus-visible:ring-ring/50 data-invalid:border-destructive data-invalid:bg-destructive/5 dark:data-invalid:bg-destructive/10"
        >
          <div className="flex items-center">
            <Input
              type="email"
              value={email}
              onChange={(event) => {
                setEmail(event.target.value)
                setError("")
                if (step === "password") setStep("email")
              }}
              placeholder="Email or phone number"
              aria-label="Email or phone number"
              aria-invalid={(step === "email" && !!error) || undefined}
              aria-describedby={`${idPrefix}-error`}
              autoComplete="username"
              className="h-14 rounded-none bg-transparent px-4 text-lg focus-visible:border-transparent focus-visible:ring-0 aria-invalid:ring-0 aria-invalid:border-transparent aria-invalid:bg-transparent dark:aria-invalid:bg-transparent"
            />
            {step === "email" && <SubmitArrow ready={!!email} busy={busy} />}
          </div>
          <div
            data-open={step === "password" ? "" : undefined}
            className="grid grid-rows-[0fr] transition-[grid-template-rows] duration-450 ease-[cubic-bezier(0.32,0.72,0,1)] data-open:grid-rows-[1fr] motion-reduce:transition-none"
          >
            <div
              inert={step !== "password"}
              className="min-h-0 overflow-hidden"
            >
              <div className="flex items-center border-t border-input">
                <Input
                  ref={passwordRef}
                  type="password"
                  value={password}
                  onChange={(event) => {
                    setPassword(event.target.value)
                    setError("")
                  }}
                  placeholder="Password"
                  aria-label="Password"
                  aria-invalid={(step === "password" && !!error) || undefined}
                  aria-describedby={`${idPrefix}-error`}
                  autoComplete="current-password"
                  tabIndex={step === "password" ? 0 : -1}
                  className="h-14 rounded-none bg-transparent px-4 text-lg focus-visible:border-transparent focus-visible:ring-0 aria-invalid:ring-0 aria-invalid:border-transparent aria-invalid:bg-transparent dark:aria-invalid:bg-transparent"
                />
                <SubmitArrow ready={!!password} busy={busy} />
              </div>
            </div>
          </div>
        </div>
        <p
          id={`${idPrefix}-error`}
          aria-live="polite"
          className="mt-2.5 min-h-5.5 text-sm text-destructive"
        >
          {error}
        </p>
        <Label className="mt-1.5 cursor-pointer gap-2 text-sm font-normal">
          <Checkbox defaultChecked />
          Remember me
        </Label>
        <div className="mt-10 flex flex-col items-center gap-2.5 text-sm">
          <a href="#forgot" className="text-primary hover:underline">
            Forgot password? ›
          </a>
          <p className="flex flex-wrap justify-center gap-x-1 text-muted-foreground">
            <span>Don&apos;t have a suiss Account?</span>
            <a href="#create" className="text-primary hover:underline">
              Create yours now ›
            </a>
          </p>
        </div>
      </form>
    </section>
  )
}
```
