Input OTP
入力、貼り付け、SMS のオートフィルに対応したワンタイムコードのスロット。コードが連鎖して入るオプトインのアニメーションと、検証中のステータスを備えています。
Type or paste 123456 to pass. Anything else fails.
pnpm dlx shadcn@latest add https://hextaui.com/r/input-otp.jsonコンポーネント、HextaUIのテーマトークン、依存するHextaUIコンポーネントを追加します。
まだ追加していない場合は、グローバルCSSにテーマトークンを追加してください。
依存関係をインストールします。
pnpm add @base-ui/react @tabler/icons-react class-variance-authority cn次のコードをコピーしてプロジェクトに貼り付けてください。
components/ui/input-otp.tsx lib/motion.ts インポートパスは、お使いのプロジェクト構成に合わせて更新してください。
1 文字につき 1 つの <InputOTPSlot /> をレンダリングし、length を同じ数に設定します。スロットは自分で位置を判断するため、同期を保つ index prop はありません。
連結
デフォルトの見た目。各 <InputOTPGroup /> が、スロットを端を共有する 1 つの帯に連結します。
分離
variant="separate" は、各スロットに角丸の独自のボックスを与え、その間に余白を設けます。
サイズ
sm、default、lg は、入力欄とボタンの高さに合っています。タッチスクリーンでは、すべてのサイズが 16px のフォントで 44px 以上に拡大します。
アニメーション付き
animated はデフォルトでオフです。オンにすると、入力した文字は下から現れ、削除した文字は沈んで消え、残りはスライドして詰まり、オートフィル、貼り付け、または独自の state によるコード全体は、スロットごとに連鎖して入ります。Fill code を押すと、連鎖を確認できます。
ステータス
status はコードの検証結果を表示します。loading はスロットをロックしてフィールドを処理中とマークし、error はすべてのスロットを不正とマークし、success は縁を緑にします。いずれも通知されます。animated を指定すると、loading は波を走らせ、error は 1 回揺れ、success は文字をポップさせます。
制御
value と onValueChange を渡します。値は常にフィルタリング済みのコードで、length を超えることはありません。
Form
name を指定すると、コードがフォームと一緒に送信されます。autoSubmit は最後のスロットが埋まった時点で送信するため、オートフィルされたコードは、もう一度タップしなくてもサインインできます。
Field と組み合わせる
<Field /> の内側では、ラベル、説明、エラーが自動で関連付けられます。000000 以外を入力すると、エラーを確認できます。
無効な値
ルートの aria-invalid はすべてのスロットをマークします。メッセージは aria-describedby で関連付けます。
文字と数字
validationType="alphanumeric" は、リカバリーコードや招待コードを受け付け、normalizeValue は入力または貼り付けの際にそれらを大文字にします。
マスク
mask は、PIN 向けに各文字を隠します。値がワンタイムコードでない場合は、autoComplete="off" でオートフィルをオフにします。
カスタムのセパレーター
スロットを好きなようにグループ化し、独自のアイコンを <InputOTPSeparator /> に渡します。
無効
無効なフィールドにはフォーカスも編集もできません。
右から左
スロットは右から埋まり、矢印キーは見た目に従います。最初以外のスロットには、翻訳した aria-label を付けてください。右から左のページでコードを左から右のままにするには、フィールドに dir="ltr" を設定します。
| キー | アクション |
|---|---|
| Tab | フォーカスをフィールド内の最初の空のスロットに移し、また外に出します。タブ順に入るスロットは 1 つだけです。 |
| ←→ | 前または次のスロットに移動します。右から左のレイアウトでは、見た目の順序に従います。 |
| Home↑ | 最初のスロットに移動します。 |
| End↓ | 最後の文字の次のスロットに移動します。 |
| Backspace | スロット内の文字を削除し、スロットが空の場合はその前の文字を削除します。後続の文字は前に詰まります。 |
| Delete | スロット内の文字を削除し、フォーカスをそこに保ちます。 |
| CtrlBackspace | コード全体をクリアします。macOS では ⌘ Backspace。 |
| CtrlA | コード全体を選択します(macOS では ⌘ A)。その後 Backspace または Delete でクリアして最初のスロットに戻り、入力または貼り付けで置き換わり、Ctrl C ですべてコピーされます。他のキーやクリックで選択は解除されます。 |
- 各スロットは本物の input です。最初のスロットは
<label>またはaria-labelから名前を取り、それ以外は「Character 2 of 6」のように名付けられます。翻訳するには、スロットにaria-labelを渡します。 - 最初のスロットには
autocomplete="one-time-code"が付いているため、iOS と macOS ではメッセージやメールからのコードが、Android では SMS のコードが提示され、パスワードマネージャーも入力できます。1 つのスロットに入ったコード全体は、すべてのスロットに振り分けられます。アニメーションによる連鎖は、WebOTP API から設定したコードを含め、すべての入力元で実行されます。 - スロットにフォーカスがある間にコードが空になったとき(誤ったコードがクリアされた後など)は、フォーカスが最初のスロットに戻るため、次の入力が正しい位置から始まります。
statusが設定されると、フィールドの隣にある非表示のライブリージョンがそれを通知します。文言はloadingLabel、successLabel、errorLabelで変更できます。animatedを指定すると、文字はスクリーンリーダーから隠されたレイヤーに描画され、入力欄は実際の値を保持します。モーションの低減が有効な場合、文字はフェードするだけで、ステータスの波は穏やかなパルスになります。
Base UI の OTP field をベースにしています。Base UI のすべての props がそのまま渡されます。
| プロパティ | 型 | デフォルト |
|---|---|---|
length必須。スロットの数。同じ数の InputOTPSlot パーツをレンダリングします。 | number | – |
variant | "joined" | "separate" | "joined" |
size | "sm" | "default" | "lg" | "default" |
animated文字を表示・非表示のアニメーションで動かし、複数文字の入力を連鎖させ、ステータスをアニメーションさせます。 | boolean | false |
statusコードの検証結果。loading の間、スロットは読み取り専用になります。 | "idle" | "loading" | "success" | "error" | – |
loadingLabel | string | "Verifying code" |
successLabel | string | "Code verified" |
errorLabel | string | "Code is incorrect" |
value | string | – |
defaultValue | string | – |
onValueChange | (value: string, details) => void | – |
onValueComplete最後のスロットが埋まったときに呼ばれます。 | (value: string, details) => void | – |
onValueInvalid入力または貼り付けられた文字が拒否されたときに呼ばれます。 | (value: string, details) => void | – |
validationType | "numeric" | "alpha" | "alphanumeric" | "none" | "numeric" |
normalizeValueフィルタリングの後に実行されます。冪等に保ってください。 | (value: string) => string | – |
inputModeデフォルトは validationType から決まります。 | string | – |
autoComplete | string | "one-time-code" |
autoSubmit | boolean | false |
mask | boolean | false |
aria-invalidすべてのスロットを不正とマークします。 | boolean | – |
name | string | – |
form | string | – |
id最初のスロットに付与されるため、ラベルの htmlFor がそれを指します。 | string | – |
disabled | boolean | false |
readOnly | boolean | false |
required | boolean | false |
className | string | (state) => string | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| 属性 | 説明 |
|---|---|
data-slot="input-otp" | ルート。 |
data-variant="joined" | "separate" | 現在のバリアント。 |
data-size | 現在のサイズ。 |
data-status | 設定されている場合のステータス。 |
data-animated | animated がオンのときに付与されます。 |
data-shake | ステータスがエラーになった後、フィールドが揺れている間、付与されます。 |
data-complete | すべてのスロットが埋まっているときに付与されます。 |
data-filled | いずれかのスロットが埋まっているときに付与されます。 |
data-focused | スロットにフォーカスがある間、付与されます。 |
data-disabled | 無効のときに存在します。 |
data-readonly | 読み取り専用のとき(読み込み中を含む)に付与されます。 |
data-required | 必須のときに付与されます。 |
data-invalid / data-valid / data-touched / data-dirty | Field 内での、フィールドの状態。 |
data-slot="input-otp-status" | 非表示のライブリージョン。ルートの兄弟要素です。 |
スロットの並びをレイアウトするプレーンな要素。joined の variant では、スロットが端を共有します。
| プロパティ | 型 | デフォルト |
|---|---|---|
render | ReactElement | (props, state) => ReactElement | <div> |
| 属性 | 説明 |
|---|---|
data-slot="input-otp-group" | CSSでグループを指定します。 |
1 つの入力欄を保持するボックス。className はボックスに、それ以外の props はすべて入力欄に渡されます。
| プロパティ | 型 | デフォルト |
|---|---|---|
aria-labelラベルを使う最初のスロットでは無視されます。 | string | "Character N of M" |
classNamestate には、スロットの index、value、filled、field の状態が含まれます。 | string | (state) => string | – |
placeholder | string | – |
| 属性 | 説明 |
|---|---|
data-slot="input-otp-slot" | ボックス。 |
data-filled | スロットに文字があるときに付与されます。 |
data-status | ルートのステータス(idle でない場合)。 |
--input-otp-index | スロットの位置。ステータスの動きをずらすのに使われます。 |
data-slot="input-otp-input" | 内側の input。Base UI の data-filled、data-focused、data-complete と field の属性を持ちます。 |
data-slot="input-otp-char" | animated のときに描画される文字。 |
マイナスアイコンのセパレーター。別のアイコンを使うには、children を渡します。
| プロパティ | 型 | デフォルト |
|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" |
className | string | (state) => string | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| 属性 | 説明 |
|---|---|
data-slot="input-otp-separator" | CSS でセパレーターを指定します。 |
スロットとグループの背後にあるクラス名(inputOTPGroupVariants)。{ variant, size } を渡して呼び出します。
- Motionすべてのコンポーネントがアニメーションに使うイージングカーブ、継続時間、モーション軽減のチェックと、サイズのモーフィングやスライドするハイライト用のフックです。
- Calendar単一、範囲、複数選択に対応する日付グリッドです。月のスライド、範囲のプレビュー、タッチしやすいサイズの日付セルを備えています。
- Checkboxチェックが描かれるように表示されるチェックボックスです。中間状態の親、グループ、ホバーを共有するラベルに対応します。
- Comboboxチップ、グループ、非同期の結果に対応した、絞り込み可能なセレクトです。入力に合わせてポップアップのサイズが変わります。
- Date picker押すとポップオーバーでカレンダーを開くボタンです。スマートフォンではボトムシートになり、単一の日付も範囲も選べます。
- Fieldコントロールに紐付いたラベル、説明、エラーです。バリデーション状態とフォーム向けのレイアウトを備えています。
使用しているブロック
Input OTP の上に構築されるブロック。
- ProfileAIプロダクトの設定にあるプロフィールセクションです。写真を円形に切り抜き、入力しながら確認されるユーザー名を選び、6桁のコードで新しいメールアドレスを確認し、サイトを認識するリンクを追加し、他の人からどう見えるかをライブのカードで確認できます。
- SecurityAIプロダクト向けのセッションとセキュリティです。行がアニメーションで消えるサインアウトつきのアクティブなデバイス、リアルタイムの強度メーターつきのパスワード変更、本物のQRコードを使う二要素認証の設定、6桁の確認とダウンロードできるリカバリーコード、WebAuthnによるパスキー、入力による確認が必要なアカウント削除を備えています。