Skip to content
fandhe-frontendcore v0.4.3
GitHub

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 です。
ArrowLeftInput前の桁へフォーカスを移します(dispatch: "prev"、先頭桁なら留まります)。
ArrowRightInput次の桁へフォーカスを移します(dispatch: "next"、最終桁なら留まります)。
BackspaceInput現在桁を消去し、前の桁へフォーカスを移します(dispatch: "backspace"、先頭桁なら消去のみで留まります)。
DeleteInput現在桁のみを消去します(dispatch: "delete"、フォーカスは移動しません)。
Control+V / Cmd+VInputクリップボードの文字列を先頭桁から一括入力します(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 時の枠線、全桁充足時の枠色、フォーカスリングの最小例です。

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;
}

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-input

API Reference

Arguments

NameTypeDefaultDescription
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)PinInputKindPinInputKind::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

PartAttributeObserved Values
hidden-inputdata-disabled(値なし)
inputdata-complete(値なし)
inputdata-disabled(値なし)
inputdata-filled(値なし)
inputdata-index0, 1, 2, 3
inputdata-invalid(値なし)
inputdata-readonly(値なし)
labeldata-complete(値なし)
labeldata-disabled(値なし)
labeldata-invalid(値なし)
labeldata-readonly(値なし)
labeldata-required(値なし)
rootdata-complete(値なし)
rootdata-disabled(値なし)
rootdata-invalid(値なし)
rootdata-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 を自前で当てられます。

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

KeyDescription
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

AttributeDescription
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 が共有する。