import {
InputOTP,
InputOTPGroup,Input OTP はセグメント化されたワンタイムコード欄です。視覚的なスロットを1つのネイティブ入力の上に重ねるため、貼り付けとモバイルのコードオートフィルがそのまま動きます。両バックエンドとも @guilherme_rodz の同じ input-otp ライブラリを包み、下層に Radix や Base のプリミティブはありません。スロットはネオブルータリストのレシピに沿ってスタイルしています。太い枠線、硬い影、太字のタイポグラフィです。
次のような場面で使います:
- 二要素認証 — パスワードサインイン後の 6 桁 TOTP または SMS コード。
- メール・電話の確認 — サインアップ、チェックアウト、アカウント復旧での所有確認。
- PIN 入力 — 画面ロックや支払い確認の 4 桁コード(Four Digits の例を参照)。
概要#
Input OTP は @guilherme_rodz が手がけた input-otp の上に構築されています。
インストール#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/input-otp.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/input-otp.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/input-otp.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/input-otp.json
以下の依存関係をインストールします。
pnpm add input-otpnpm install input-otpyarn add input-otpbun add input-otp
以下のコードをコピーして、プロジェクトに貼り付けます。
"use client"
import * as React from "react"
import { OTPInput, OTPInputContext } from "input-otp"
import { MinusIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function InputOTP({
className,
containerClassName,
...props
}: React.ComponentProps<typeof OTPInput> & {
containerClassName?: string
}) {
return (
<OTPInput
data-slot="input-otp"
containerClassName={cn(
"cn-input-otp flex items-center has-disabled:opacity-50",
containerClassName
)}
spellCheck={false}
className={cn("disabled:cursor-not-allowed", className)}
{...props}
/>
)
}
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="input-otp-group"
className={cn(
"flex items-center rounded has-aria-invalid:border-destructive",
className
)}
{...props}
/>
)
}
function InputOTPSlot({
index,
className,
...props
}: React.ComponentProps<"div"> & {
index: number
}) {
const inputOTPContext = React.useContext(OTPInputContext)
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
return (
<div
data-slot="input-otp-slot"
data-active={isActive}
className={cn(
"relative flex size-8 items-center justify-center border-y-2 border-r-2 bg-input text-sm shadow-sm transition-all outline-none first:rounded-l first:border-l-2 last:rounded-r aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:outline-2 data-[active=true]:outline-primary data-[active=true]:aria-invalid:border-destructive",
className
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
</div>
)}
</div>
)
}
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="input-otp-separator"
className="flex items-center [&_svg:not([class*='size-'])]:size-4"
role="separator"
{...props}
>
<MinusIcon />
</div>
)
}
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
インポートのパスをプロジェクトの構成に合わせて更新します。
使い方#
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp"<InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>コンポジション#
InputOTP を構築するには、次のコンポジションを使用します。
InputOTP
├── InputOTPGroup
│ ├── InputOTPSlot
│ ├── InputOTPSlot
│ └── InputOTPSlot
├── InputOTPSeparator
├── InputOTPGroup
│ ├── InputOTPSlot
│ ├── InputOTPSlot
│ └── InputOTPSlot
├── InputOTPSeparator
└── InputOTPGroup
├── InputOTPSlot
└── InputOTPSlotPattern#
pattern プロパティを使って、OTP 入力にカスタムパターンを定義します。
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"
;<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
...
</InputOTP>"use client"
import { REGEXP_ONLY_DIGITS } from "input-otp"使用例#
区切り#
<InputOTPSeparator /> コンポーネントを使って、入力グループの間に区切りを追加します。
import {
InputOTP,
InputOTPGroup,無効#
disabled プロパティを使って、入力を無効にします。
import { Field, FieldLabel } from "@/components/ui/field"
import {
InputOTP,制御された入力#
value プロパティと onChange プロパティを使って、入力値を制御します。
"use client"
import * as React from "react"エラー状態#
スロットに aria-invalid を指定して、エラー状態を表示します。
"use client"
import * as React from "react"4 桁#
PIN コードでよく使われるパターンです。pattern={REGEXP_ONLY_DIGITS} プロパティを使用します。
"use client"
import { REGEXP_ONLY_DIGITS } from "input-otp"英数字#
REGEXP_ONLY_DIGITS_AND_CHARS を使って、英字と数字の両方を受け付けます。
"use client"
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"フォーム#
import { RefreshCwIcon } from "lucide-react"
import { Button } from "@/components/ui/button"RTL#
Neobrutalism で RTL を有効にする方法は、RTL 設定ガイドをご覧ください。
"use client"
import * as React from "react"アクセシビリティ#
OTP 欄に WAI-ARIA APG パターンはなく、必要もありません。input-otp は視覚スロットの背後に autocomplete="one-time-code" 付きの本物の <input> を1つ描画するため、支援技術は単一のテキストフィールドとして読み上げ、モバイルキーボードは届いたコードを提案できます。スロットは見た目専用です。フィールド自体にラベル(FormLabel または aria-label)を付け、エラー表示にはスロットへ aria-invalid を渡してください。
キーボード操作はネイティブのテキストフィールドに沿い、Radix と Base のバリアントで同一です:
| キー | 操作 |
|---|---|
Tab / Shift + Tab | フィールドへ/からフォーカスを移動(タブストップは1つ) |
ArrowLeft / ArrowRight | スロット間でキャレットを移動 |
Home / End | 最初/最後のスロットへジャンプ |
Backspace / Delete | 前/次の文字を削除 |
Ctrl/Cmd + V | 貼り付け — pattern に合うコードならスロットに埋まります |
APIリファレンス#
詳しくは input-otp のドキュメントをご覧ください。