# Verify Code

> A two-factor authentication screen with a six-digit code entry, error feedback, and a resend countdown.

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/verify-code).

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

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/esuiss/verify-code.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/input-otp`

## Files

### `components/blocks/verify-code.tsx`

```tsx
"use client"

import * as React from "react"

import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
import {
  InputOTP,
  InputOTPGroup,
  InputOTPSlot,
} from "@/components/ui/input-otp"

const DEMO_CODE = "482913"

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

const slotClass =
  "h-14 w-11 rounded-xl sm:h-15.5 sm:w-12.5 border border-input bg-transparent text-3xl transition-[border-color,box-shadow,scale] duration-250 ease-[cubic-bezier(0.3,1.25,0.5,1)] data-[active=true]:border-primary data-[active=true]:ring-3 data-[active=true]:ring-primary/25 group-data-[status=error]/code:border-destructive group-data-[status=ok]/code:border-green-500"

export function VerifyCode() {
  const idPrefix = React.useId()
  const [code, setCode] = React.useState("")
  const [status, setStatus] = React.useState<"idle" | "error" | "ok">("idle")
  const [left, setLeft] = React.useState(30)
  const groupRef = React.useRef<HTMLDivElement>(null)

  React.useEffect(() => {
    if (left <= 0) return
    const timer = window.setTimeout(() => setLeft(left - 1), 1000)
    return () => window.clearTimeout(timer)
  }, [left])

  const complete = (value: string) => {
    window.setTimeout(() => {
      if (value === DEMO_CODE) return setStatus("ok")
      setStatus("error")
      if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches)
        groupRef.current?.animate(SHAKE, { duration: 420, easing: "ease-out" })
      window.setTimeout(() => {
        setCode("")
        setStatus("idle")
      }, 800)
    }, 200)
  }

  if (status === "ok") {
    return (
      <section className="flex min-h-140 items-center justify-center p-6">
        <div className="flex max-w-105 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">
          <span className="flex size-19 items-center justify-center rounded-full bg-[color-mix(in_oklab,var(--color-green-500),var(--foreground)_25%)] text-white dark:bg-[color-mix(in_oklab,var(--color-green-500),black_35%)]">
            <IconPlaceholder
              lucide="CheckIcon"
              tabler="IconCheck"
              hugeicons="Tick02Icon"
              phosphor="CheckIcon"
              remixicon="RiCheckLine"
              className="size-9.5"
            />
          </span>
          <h1 className="text-4xl font-semibold tracking-tight">Verified.</h1>
          <p className="text-lg text-muted-foreground">
            This browser is now trusted. You won&apos;t be asked for a code next
            time.
          </p>
          <button
            type="button"
            onClick={() => {
              setCode("")
              setStatus("idle")
            }}
            className="rounded-xs text-lg text-primary outline-none hover:underline focus-visible:ring-3 focus-visible:ring-ring/50"
          >
            Start over ›
          </button>
        </div>
      </section>
    )
  }

  return (
    <section className="flex min-h-140 items-center justify-center p-6">
      <div className="flex flex-col items-center text-center">
        <span className="flex size-16 items-center justify-center rounded-full bg-muted text-primary">
          <IconPlaceholder
            lucide="LockIcon"
            tabler="IconLock"
            hugeicons="SquareLock02Icon"
            phosphor="LockSimpleIcon"
            remixicon="RiLockLine"
            className="size-7.5"
          />
        </span>
        <h1 className="mt-5 text-4xl font-semibold tracking-tight text-balance md:text-5xl">
          Two-factor authentication
        </h1>
        <p className="mt-3 mb-7.5 max-w-110 text-lg text-muted-foreground">
          Enter the 6-digit code sent to your trusted devices.
        </p>
        <div ref={groupRef}>
          <InputOTP
            autoFocus
            maxLength={6}
            value={code}
            onChange={(value) => {
              setCode(value)
              setStatus("idle")
            }}
            onComplete={complete}
            aria-label="Verification code"
            aria-describedby={`${idPrefix}-hint`}
          >
            <div data-status={status} className="group/code flex gap-4">
              <InputOTPGroup className="gap-2.5">
                {[0, 1, 2].map((index) => (
                  <InputOTPSlot
                    key={index}
                    index={index}
                    className={slotClass}
                  />
                ))}
              </InputOTPGroup>
              <InputOTPGroup className="gap-2.5">
                {[3, 4, 5].map((index) => (
                  <InputOTPSlot
                    key={index}
                    index={index}
                    className={slotClass}
                  />
                ))}
              </InputOTPGroup>
            </div>
          </InputOTP>
        </div>
        <p
          id={`${idPrefix}-hint`}
          aria-live="polite"
          data-status={status}
          className="mt-3.5 min-h-5 text-sm text-muted-foreground data-[status=error]:text-destructive"
        >
          {status === "error" ? "Incorrect code." : `Demo code: ${DEMO_CODE}`}
        </p>
        <div className="mt-6 flex flex-col items-center gap-2.5 text-sm">
          {left > 0 ? (
            <span className="text-muted-foreground tabular-nums">
              Resend code · 0:{String(left).padStart(2, "0")}
            </span>
          ) : (
            <button
              type="button"
              onClick={() => setLeft(30)}
              className="rounded-xs text-primary outline-none hover:underline focus-visible:ring-3 focus-visible:ring-ring/50"
            >
              Resend code ›
            </button>
          )}
          <a href="#help" className="text-primary hover:underline">
            Didn&apos;t get a verification code? ›
          </a>
        </div>
      </div>
    </section>
  )
}
```
