Skip to content
All Taste components

Mobile screens

Sign in with OTP

Enter the six-digit code sent to a phone number, with a resend timer and a number pad.

6 code boxes · filled / focused / empty · resend countdown · disabled verify · number pad

Enter the code

We sent a 6-digit code to +91 98450 12321. Change

Didn’t get it? Resend in 0:24

// Mobile-first page; the same markup maps to React Native via NativeWind (View/TextInput/Pressable).
"use client";
import { useRef, useState } from "react";
import { ChevronLeft } from "lucide-react";
import { cn } from "@/lib/utils";

const LENGTH = 6;
const phone = "+91 98450 12321";

export default function VerifyCodeScreen() {
  const [code, setCode] = useState<string[]>(Array(LENGTH).fill(""));
  const [seconds] = useState(24);
  const inputs = useRef<(HTMLInputElement | null)[]>([]);
  const complete = code.every(Boolean);

  const setDigit = (i: number, value: string) => {
    const digit = value.replace(/[^0-9]/g, "").slice(-1);
    setCode((c) => c.map((d, j) => (j === i ? digit : d)));
    if (digit && i < LENGTH - 1) inputs.current[i + 1]?.focus();
  };

  return (
    <main className="mx-auto flex min-h-dvh max-w-md flex-col bg-bg font-sans text-fg">
      <header className="flex h-12 items-center px-2">
        <button type="button" aria-label="Back" className="grid size-11 place-items-center rounded-full hover:bg-surface-2">
          <ChevronLeft className="size-[22px]" />
        </button>
      </header>

      <form className="px-4 pt-1" onSubmit={(e) => e.preventDefault()}>
        <h1 className="text-2xl font-semibold tracking-tight">Enter the code</h1>
        <p className="mt-2 text-sm text-fg-muted">
          We sent a 6-digit code to <b className="font-medium tabular-nums text-fg">{phone}</b>.{" "}
          <a href="/sign-in" className="font-medium text-primary">Change</a>
        </p>

        <div role="group" aria-label="One-time code" className="mt-6 grid grid-cols-6 gap-[7px]">
          {code.map((d, i) => (
            <input
              key={i}
              ref={(el) => { inputs.current[i] = el; }}
              value={d}
              onChange={(e) => setDigit(i, e.target.value)}
              onKeyDown={(e) => { if (e.key === "Backspace" && !d && i > 0) inputs.current[i - 1]?.focus(); }}
              inputMode="numeric"
              autoComplete={i === 0 ? "one-time-code" : "off"}
              maxLength={1}
              aria-label={"Digit " + (i + 1)}
              className="h-[52px] w-full rounded border border-border-strong bg-bg text-center text-[22px] font-semibold tabular-nums outline-none focus:border-primary focus:ring-[3px] focus:ring-ring"
            />
          ))}
        </div>

        <p className="mt-3 text-xs text-fg-muted">
          Didn’t get it?{" "}
          {seconds > 0
            ? <>Resend in <span className="font-medium tabular-nums text-fg">0:{String(seconds).padStart(2, "0")}</span></>
            : <button type="button" className="font-medium text-primary">Resend code</button>}
        </p>

        <button
          type="submit"
          disabled={!complete}
          className={cn("mt-5 h-12 w-full rounded bg-primary text-[15px] font-semibold text-primary-fg hover:bg-primary-hover", "disabled:pointer-events-none disabled:opacity-50")}
        >
          Verify
        </button>
      </form>
    </main>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.