Pin Input
fandhe-frontend-headless-ui の pin_input mod が提供する PIN/OTP 桁入力の unstyled 部品です。Root / Label / Control / Input(桁ごと)/ HiddenInput の 5 anatomy パーツと、桁ごとの値・フォーカス位置・complete 判定を担う独自の値状態機械を持ちます。data-complete は全桁充足時のみの存在属性として一元管理されます。
/themes/ 側の PinInput(Chakra ライクな variant/size を持つスタイル層)と異なり、CSS を一切持ちません。
PinInputProps(disabled/readonly/invalid/required)は次のように各パーツへ反映されます。root/label/input は data-disabled/data-invalid/data-readonly を、label はこれら 3 つに加えて data-required を付与します。input にはさらに data-index(0-origin の桁インデックス)と data-filled(値が非空のときのみ)が付きます。invalid: true のとき input へ aria-invalid="true" を(valid のときは属性自体を省略)、readonly: true のとき input へネイティブ readonly 存在属性を付与します(type="hidden" の hidden_input には readonly/required いずれも効果がないため付与しません)。
キーボード操作
| キー | 対象 | 効果 |
|---|---|---|
0-9 / a-z / A-Z(kind 適合文字) | Input | 現在のフォーカス桁(未設定なら先頭の空き桁)へ入力し、次桁が存在すればそこへ前進します(dispatch: "input")。種別不適合文字は no-op です。 |
| ArrowLeft | Input | 前の桁へフォーカスを移します(dispatch: "prev"、先頭桁なら留まります)。 |
| ArrowRight | Input | 次の桁へフォーカスを移します(dispatch: "next"、最終桁なら留まります)。 |
| Backspace | Input | 現在桁を消去し、前の桁へフォーカスを移します(dispatch: "backspace"、先頭桁なら消去のみで留まります)。 |
| Delete | Input | 現在桁のみを消去します(dispatch: "delete"、フォーカスは移動しません)。 |
| Control+V / Cmd+V | Input | クリップボードの文字列を先頭桁から一括入力します(dispatch: "paste")。全文字が kind に適合する場合のみ受理し、1 文字でも不適合なら部分適用せず一切拒否します。 |
ポインタ・キーボード操作の実際の DOM 配線(keydown/paste イベントハンドラ、fandhe-frontend-wasm-full)は本部品のスコープ外です。本部品は SSR 静的マークアップと dispatch 契約のみを提供します。
参考サイトとの差分
ark-ui 公式 Data Attributes / Keyboard Support 表・Radix one-time-password-field と突合し、上記のキーボード操作・data-*/aria-* 語彙へ揃えました。一方、以下は意図的に合わせていません。
- Radix の
data-orientation(root、既定vertical)はレイアウト関心のため追加しません(headless-ui の責務境界、docs/policy/intentional-non-adoption.md§3.25 規則 2)。 - Radix の
role="group"(root)は ark-ui を主参照としているため付与しません。必要なら呼び出し側がattrs経由で追加できます。 - zag
connectのみが持つ Home/End・同一キー再入力での前進(ADVANCE)・Enterによる自動送信(autoSubmit)・blurOnComplete/selectOnFocus/sanitizeValue/patternは、クライアント DOM 配線・アプリロジックの関心のため対象外です。 inputmode(otp || numericのとき常にnumericを強制する zag の挙動)・autocomplete="off"の明示・enterkeyhint/autocapitalizeは native ヒントの最小主義判断を維持し、追随していません。
スタイル済みの表示例は Pin Input を参照してください。
関連 API: fandhe-frontend-headless-ui API
自前 CSS の最小例
Themes 版を使わず本部品を直接使う場合、[data-scope="pin-input"][data-part="..."] セレクタでスタイルを当てます。以下は input の枠線・寸法、値入力済み時の強調、invalid 時の枠線、全桁充足時の枠色、フォーカスリングの最小例です。
[data-scope="pin-input"][data-part="input"] {
width: 2.5rem;
height: 2.5rem;
text-align: center;
border: 1px solid #ccc;
}
[data-scope="pin-input"][data-part="input"][data-filled] {
border-color: #0a7;
}
[data-scope="pin-input"][data-part="input"][data-invalid] {
border-color: #d33;
}
[data-scope="pin-input"][data-part="root"][data-complete] [data-part="input"] {
border-color: #06c;
}
[data-scope="pin-input"][data-part="input"]:focus-visible {
outline: 2px solid #06c;
outline-offset: 2px;
}Demo
枠線・余白は docs サイト側のデモ枠であり、fandhe-frontend-headless-ui 自体はスタイルを持ちません。
Features
- Root / Label / Control / Input(桁ごと)/ HiddenInput の 5 anatomy パーツと、桁ごとの値・フォーカス位置・complete 判定を担う独自状態機械を提供する。
PinInputProps(disabled/readonly/invalid/required)が root/label/input の状態束を一元管理する(イシュー #1615 で新設)。data-completeは全桁充足時のみの存在属性として本モジュールが独自に一元管理する。- 各桁
inputはaria-label(例:"PIN digit 1 of 6")・data-index(桁インデックス)・data-filled(値が非空のときのみ)を必ず付与し、スクリーンリーダー利用者・CSS の双方が桁位置・入力状態を把握できるようにする。kind(Numeric/Alphanumeric/Alphabetic)が文字種検証とinputmodeの両方を決める。 - キーボード操作は ark-ui Keyboard Support 表に合わせ、Backspace は「現在桁を消去し前の桁へ移動」、Delete は「現在桁のみ消去(フォーカス移動なし)」、ArrowLeft/ArrowRight は隣接桁へのフォーカス移動として区別される(dispatch:
"backspace"/"delete"/"prev"/"next"、イシュー #1615)。 - 秘密値の SSR プレフィルは非推奨(
hidden_inputの連結値・各桁valueは HTML ソースに平文で現れるため、実際の OTP を初期値として埋め込む用途には使わないこと)。
Anatomy
root
label
control
input
hidden-inputAPI Reference
Arguments
| Name | Type | Default | Description |
|---|---|---|---|
root/label(complete) | bool | — | data-complete 存在属性を反映する。 |
root/label/input(props) | &PinInputProps | — | disabled/readonly/invalid/required の状態束。root/label/input へ data-disabled/data-invalid/data-readonly を、label にのみ data-required を追加で付与する(イシュー #1615)。 |
input(index) | usize | — | 0-origin の桁インデックス(aria-label/data-index に使う)。 |
input(count) | usize | — | 全桁数(aria-label の分母に使う)。 |
input(value) | &str | — | 当該桁の値(空文字列 = 未入力、1 文字 = 入力済み。data-filled の判定にも使う)。 |
input(kind) | PinInputKind | PinInputKind::Numeric | 文字種別。inputmode 属性と文字検証の両方を決める。 |
input(mask) | bool | — | true で type="password"(表示マスク)、false で type="text"。 |
input(otp) | bool | — | true のとき autocomplete="one-time-code"(WebOTP/SMS 自動入力連携)を付与する。 |
hidden_input(name) | &str | — | フォーム送信名。各桁 input は name を持たないため、実送信値はこのパーツのみが担う。 |
hidden_input(value) | &str | — | 全桁の連結値。 |
Data Attributes
| Part | Attribute | Observed Values |
|---|---|---|
hidden-input | data-disabled | (値なし) |
input | data-complete | (値なし) |
input | data-disabled | (値なし) |
input | data-filled | (値なし) |
input | data-index | 0, 1, 2, 3 |
input | data-invalid | (値なし) |
input | data-readonly | (値なし) |
label | data-complete | (値なし) |
label | data-disabled | (値なし) |
label | data-invalid | (値なし) |
label | data-readonly | (値なし) |
label | data-required | (値なし) |
root | data-complete | (値なし) |
root | data-disabled | (値なし) |
root | data-invalid | (値なし) |
root | data-readonly | (値なし) |
Examples
Alphanumeric, unmasked
PinInputKind::Alphanumeric + mask: false の招待コード用途。Numeric 既定と異なり inputmode 属性は出力されない。
Custom CSS (invalid state)
headless-ui はスタイルレスのため、data-invalid/data-filled/data-complete セレクタを使った自前 CSS の最小例を示す。
headless-ui はスタイルレスです。data-scope/data-part/data-* をセレクタに使い、以下のような CSS を自前で当てられます。
[data-scope="pin-input"][data-part="input"] {
width: 2.5rem;
height: 2.5rem;
text-align: center;
border: 1px solid #ccc;
}
[data-scope="pin-input"][data-part="input"][data-filled] {
border-color: #0a7;
}
[data-scope="pin-input"][data-part="input"][data-invalid] {
border-color: #d33;
}
[data-scope="pin-input"][data-part="root"][data-complete] [data-part="input"] {
border-color: #06c;
}
[data-scope="pin-input"][data-part="input"]:focus-visible {
outline: 2px solid #06c;
outline-offset: 2px;
}Accessibility
Keyboard Interactions
| Key | Description |
|---|---|
| 0-9 / a-z / A-Z(kind 適合文字) | 現在のフォーカス桁(未設定なら先頭の空き桁)へ入力し、次桁が存在すればそこへ前進する(dispatch: "input")。種別不適合文字は no-op(fail-closed)。 |
| ArrowLeft | 前の桁へフォーカスを移す(dispatch: "prev"、先頭桁なら留まる)。 |
| ArrowRight | 次の桁へフォーカスを移す(dispatch: "next"、最終桁なら留まる)。 |
| Backspace | 現在桁を消去し、前の桁へフォーカスを移す(dispatch: "backspace"、先頭桁なら消去のみで留まる。ark-ui Keyboard Support 表準拠、イシュー #1615 で是正)。 |
| Delete | 現在桁のみを消去する(dispatch: "delete"、フォーカスは移動しない)。 |
| Control+V / Cmd+V | クリップボードの文字列を先頭桁から一括入力する(dispatch: "paste")。全文字が kind に適合する場合のみ受理し、1 文字でも不適合なら部分適用せず一切拒否する。 |
| (実 DOM 配線) | 上記キー入力から dispatch への実配線(keydown/paste イベントの実 DOM ハンドラ)は fandhe-frontend-wasm-full 側の責務であり、本クレートのスコープ外(モジュール doc「スコープ外」節参照)。 |
WAI-ARIA
| Attribute | Description |
|---|---|
| aria-label (input) | "PIN digit {index+1} of {count}" を各桁ごとに動的生成する(render() の既定エスケープを経由するため注入経路にはならない)。 |
| aria-invalid (input) | props.invalid が true のときのみ "true" を出力する(valid のときは属性自体を省略する、イシュー #1615)。 |
| data-index (input) | 0-origin の桁インデックス(ark-ui/Radix 双方が持つ語彙、イシュー #1615)。 |
| data-filled (input) | 当該桁の値が非空のときのみの存在属性(イシュー #1615)。 |
| data-complete | 全桁充足時のみの存在属性。root/label/input が共有する。 |