Frontend Lab
← 回到所有模式
PE06⌨️ Input · 進階輸入OTP

OTP Input

OTP 驗證碼

難度 ★★★★★標籤 otp · paste · 焦點3 min read

模式簡述

OTP Input:6 格分離的驗證碼輸入。Apple、Google 都這樣做。要支援自動跳格、整段貼上、autofill。

驗證碼已寄到 +886 ****1234

|6 格全空,焦點在第 1 格

Claude Code Prompt

💬 Claude Code Prompt
參考:PE06 otp
組件:OTP.tsx

OTP Input:6 格分離的驗證碼輸入。Apple、Google 都這樣做。要支援自動跳格、整段貼上、autofill。

限制:
- 沒處理 paste
- autofill 沒寫
- 錯誤後沒清空

完整程式碼

OTP.tsx
"use client";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";

type State = "empty" | "typing" | "complete" | "error";

export function OTP({ state = "empty" }: { state?: State }) {
const init =
  state === "complete" || state === "error"
    ? ["4", "2", "0", "6", "9", "1"]
    : state === "typing"
      ? ["4", "2", "0", "", "", ""]
      : ["", "", "", "", "", ""];

const [vals, setVals] = useState<string[]>(init);
useEffect(() => setVals(init), [state]);

const focusIdx = vals.findIndex((v) => v === "");

return (
  <div className="w-full max-w-md">
    <p className="mb-3 font-mono text-[11px] text-muted-foreground">驗證碼已寄到 +886 ****1234</p>
    <div className="flex justify-center gap-2">
      {vals.map((v, i) => (
        <div
          key={i}
          className={cn(
            "grid h-12 w-10 place-items-center rounded-md border bg-background/50 font-mono text-lg transition-colors",
            v ? "border-border text-foreground" : "border-border/60 text-muted-foreground",
            i === focusIdx && state === "typing" && "border-lab-accent ring-2 ring-lab-accent/30",
            state === "complete" && "border-emerald-500/60",
            state === "error" && "border-red-500/70 text-red-500",
          )}
        >
          {v || (i === focusIdx && state === "typing" ? "_" : "")}
        </div>
      ))}
    </div>
    {state === "error" && (
      <p className="mt-3 text-center font-mono text-[11px] text-red-500">驗證碼錯誤,請重試</p>
    )}
    {state === "complete" && (
      <p className="mt-3 text-center font-mono text-[11px] text-emerald-500">驗證成功 ✓</p>
    )}
  </div>
);
}

何時用

  • ✅ 手機驗證碼登入
  • ✅ 兩步驟驗證
  • ✅ 硬體 token 驗證

何時別用

  • ❌ 驗證碼超過 8 位(直接用一般輸入)
  • ❌ 驗證碼包含字母大小寫(分格反而難)
  • ❌ 桌面端為主時(手機優化形式)

我踩過的坑

  1. 沒處理 paste:使用者複製 6 位數要貼一格貼一次。要攔截 paste 自動分配。

  2. autofill 沒寫:iOS 簡訊 OTP 應該自動填,name=‘one-time-code’ + autocomplete=‘one-time-code’。

  3. 錯誤後沒清空:使用者重打要先刪掉。reset + focus 第一格。

相關模式

  • PA05
  • PE03

搜尋

按 ⌘K 隨時開啟