Skip to content
fandhe-frontendcore v0.4.3
GitHub

Tags Input

fandhe-frontend-headless-ui の tags_input mod が提供するタグ配列入力の unstyled 部品です。Root / Label / Control / Input / Item / ItemPreview / ItemText / ItemInput / ItemDeleteTrigger / ClearTrigger / HiddenInput / LiveRegion の 12 anatomy パーツを持ち、タグ文字列の可変長リスト + 重複拒否 + 上限 + 編集中/キーボード強調インデックスを表現する値状態機械を備えます。重複・カンマ含有・空文字列のタグはすべての入口で拒否されます。LiveRegion はタグ数の変化を aria-live="polite" で支援技術へ通知します(テキスト更新の実配線は wasm-full の後続責務)。

/themes/ 側の TagsInput(Chakra ライクな variant/size を持つスタイル層)と異なり、CSS を一切持ちません。

TagsInputProps(disabled/readonly/invalid/required)は root/label/control/input/clear-trigger/hidden-input へ data-disabled/data-invalid/data-readonly を、label にはさらに data-required を反映します。invalid: true のとき input へ aria-invalid="true"(valid のときは属性自体を省略)、readonly: true のとき input へネイティブ readonly 存在属性を付与します。タグ 1 個分の状態は TagItem(value/disabled/editing/highlighted)で表し、item/item_preview に data-value(タグ文字列)、item_preview/item_text/item_delete_trigger に data-highlighted を反映します。編集モードでは item_preview が hidden、item_input が表示されます。

キーボード操作

キー対象効果
ArrowLeftControlキーボード強調(highlight)を 1 つ前へ移動します(dispatch: "highlight-prev")。強調なしなら末尾タグへ、先頭タグでそれ以上前進しません。
ArrowRightControl強調を 1 つ後ろへ移動します(dispatch: "highlight-next")。末尾タグ強調中は強調解除(入力欄へ戻る)。強調なしは no-op です。
BackspaceInput/Control強調中ならそのタグを削除、なければ末尾タグを削除します(dispatch: "backspace")。
DeleteControl強調中のタグを削除します(dispatch: "delete-highlighted")。
EnterControl/ItemPreview強調中なら編集開始("edit-start")、入力欄にフォーカスがあれば入力中の値を追加("add")します。振り分けはクライアント側の責務です。
EscapeControl強調を解除します(dispatch: "highlight-clear")、または編集中なら編集を破棄します("edit-cancel")。

上記キーの実際の DOM 配線(keydown/paste イベントハンドラ、fandhe-frontend-wasm-full)は本部品のスコープ外です。本部品は SSR 静的マークアップと dispatch 契約のみを提供し、キー入力を検出して自動的に dispatch する配線は未実装です。

参考サイトとの差分

Zag.js tags-input.connect.ts(ark-ui 基盤)・ark-ui 公式 Data Attributes / Keyboard Support 表と突合し、data-*/TagsInputProps/TagItem・上記のキーボード操作の dispatch 契約へ揃えました。一方、以下は意図的に合わせていません。

  • role="listbox"(control)/role="option"(item_preview)は撤去しました。zag/ark の control・item-preview はいずれも role を持たず、role="listbox" の許容子は option/group のみだが control は <input type="text"> を内包しており content model 違反だったため是正しました。アクセシブルネームは label の for 関連付けが担います。
  • data-focus(root/control、DOM フォーカスという ephemeral な状態)は SSR では決定できないため不採用です。
  • data-empty(root/input のタグ 0 件)は自由関数 root がタグ列を保持しないため未実装です(別 issue 候補)。
  • readonly の DOM 表現は zag(control の tabindex="0" 維持 + input へネイティブ disabled)と異なり、input へネイティブ readonly を採用します(pin-input/password-input/file-upload の直近精査と同じ規約へのリポ内一貫性を優先した判断)。
  • maxLength(タグ文字数上限)・validate コールバック・delimiter/addOnPaste/blurBehavior/allowOverflow/autoFocus/dir はアプリロジック・クライアント配線の関心のため対象外です(docs/policy/intentional-non-adoption.md §3.25)。

スタイル済みの表示例は Tags Input を参照してください。

関連 API: fandhe-frontend-headless-ui API

自前 CSS の最小例

Themes 版を使わず本部品を直接使う場合、[data-scope="tags-input"][data-part="..."] セレクタでスタイルを当てます。以下は control の枠線・折り返し、強調タグの outline、invalid 時の枠線、readonly 時の背景の最小例です。

CSS
[data-scope="tags-input"][data-part="control"] {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  border: 1px solid #ccc;
  padding: 0.25rem;
}

[data-scope="tags-input"][data-part="item-preview"][data-highlighted] {
  outline: 2px solid #2563eb;
}

[data-scope="tags-input"][data-part="input"][data-invalid] {
  border-color: #dc2626;
}

[data-scope="tags-input"][data-part="control"][data-readonly] {
  background: #f3f4f6;
}

Demo

枠線・余白は docs サイト側のデモ枠であり、fandhe-frontend-headless-ui 自体はスタイルを持ちません。

rust
2 tags
staging
a

Features

  • Root / Label / Control / Input / Item / ItemPreview / ItemText / ItemInput / ItemDeleteTrigger / ClearTrigger / HiddenInput / LiveRegion の 12 anatomy パーツと、タグ文字列の可変長リスト + 重複拒否 + 上限 + 編集中/キーボード強調インデックスを持つ値状態機械を提供する。
  • 不変条件「重複タグなし・len() <= max・カンマを含まない・空文字列を含まない」を破る入力は dispatch・hydration・コンストラクタのすべての入口で一貫して拒否する(カンマ・空文字列はフォーム送信値のカンマ結合時に曖昧さを生むため、Cursor Bugbot 指摘を踏まえ全入口で禁止)。
  • TagsInputProps(disabled/readonly/invalid/required)が root/label/control/input/clear_trigger/hidden_input の data-* を一元管理する。control/item_preview は role を持たない(イシュー #1623 で zag/ark 準拠へ是正、旧実装の role="listbox"/role="option" は撤去。アクセシブルネームは label の for 関連付けが担う)。
  • TagItem(value/disabled/editing/highlighted)が item 系 5 パーツ共通の状態束。item/item_preview に data-value(タグ文字列)を出力する。
  • editing/highlighted(編集中インデックス・キーボード強調インデックス)はいずれも ephemeral な DOM 状態のため hydration では運ばない。
  • live_region はタグ数の変化を通知する live region(role="status" + aria-live="polite" 固定、root 直下を推奨配置とする。テキスト更新の実配線は fandhe-frontend-wasm-full の後続責務、イシュー #1069)。

Anatomy

root
  label
  control
    item
      item-preview
        item-text
        item-delete-trigger
      item-input
    input
  clear-trigger
  hidden-input
  live-region

API Reference

Arguments

NameTypeDefaultDescription
TagsInputProps { disabled, readonly, invalid, required }struct—root/label/control/input/clear_trigger/hidden_input へ渡す共通状態束。
TagItem { value, disabled, editing, highlighted }struct—item/item_preview/item_text/item_input/item_delete_trigger へ渡す共通状態束。
item/item_preview(item)&TagItem—data-value/data-disabled/data-editing(item のみ)/data-highlighted(item_preview のみ)へ反映する。item_preview は編集中に hidden を出力する。
item_input(item, value)&TagItem, &str—編集中の暫定値。非編集時は hidden 存在属性のみを出力する(矢印キー操作・編集開始配線が未実装のため tabindex は固定付与せず既定の Tab 到達性を維持する)。
item_delete_trigger(item)&TagItem—aria-label を "Delete tag {value}"(zag 既定訳)として動的生成する。tabindex は固定付与しない(矢印キー操作が未実装のため既定の Tab 到達性を維持する)。
input(props, value, at_max)&TagsInputProps, &str, bool—新規タグ入力用のネイティブ入力欄。at_max は data-invalid + aria-invalid="true" を出力する。autocomplete="off"/autocorrect="off"/autocapitalize="none"/enterkeyhint="done" を固定付与する。
hidden_input(props, name, value)&TagsInputProps, &str, &str—フォーム送信名と、全タグのカンマ結合値。
live_region(children)Vec<Node>—role="status"/aria-live="polite"/aria-atomic="true" を固定付与する live region。通知文言は children として呼び出し側が渡す(イシュー #1069)。

Data Attributes

PartAttributeObserved Values
controldata-invalid(値なし)
controldata-readonly(値なし)
hidden-inputdata-invalid(値なし)
hidden-inputdata-readonly(値なし)
inputdata-empty(値なし)
inputdata-invalid(値なし)
inputdata-readonly(値なし)
itemdata-editing(値なし)
itemdata-valuea, rust, staging, wasm
item-delete-triggerdata-highlighted(値なし)
item-previewdata-highlighted(値なし)
item-previewdata-valuea, rust, staging, wasm
item-textdata-highlighted(値なし)
labeldata-invalid(値なし)
labeldata-readonly(値なし)
rootdata-invalid(値なし)
rootdata-readonly(値なし)

Examples

At max (invalid input)

上限に達した状態の input(props.invalid: true, at_max: true)。data-invalid + aria-invalid="true" が新規タグ入力欄に出力される。

rust
wasm

Read-only

TagsInputProps::readonly を反映した状態。input にネイティブ readonly + data-readonly が出力される。

staging

自前 CSS の最小例

headless-ui はスタイルレスであるため、[data-scope="tags-input"][data-part="..."] セレクタで CSS を当てるのは利用者の責務である(このページには適用しない)。

CSS
[data-scope="tags-input"][data-part="control"] {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  border: 1px solid #ccc;
  padding: 0.25rem;
}

[data-scope="tags-input"][data-part="item-preview"][data-highlighted] {
  outline: 2px solid #2563eb;
}

[data-scope="tags-input"][data-part="input"][data-invalid] {
  border-color: #dc2626;
}

[data-scope="tags-input"][data-part="control"][data-readonly] {
  background: #f3f4f6;
}

Accessibility

Keyboard Interactions

KeyDescription
ArrowLeft強調(highlight)を 1 つ前へ移動する("highlight-prev" dispatch)。**DOM 配線は wasm-full の別イシュー**(本モジュールは dispatch を受けた際の状態遷移のみを提供する契約、まだ keydown ハンドラは実装されていない)。
ArrowRight強調を 1 つ後ろへ移動する("highlight-next" dispatch)。末尾タグ強調中なら入力欄へ戻る。DOM 配線は wasm-full の別イシュー(同上)。
Backspace強調中ならそのタグを削除、なければ末尾タグを削除する("backspace" dispatch)。DOM 配線は wasm-full の別イシュー(同上)。
Delete強調中のタグを削除する("delete-highlighted" dispatch)。DOM 配線は wasm-full の別イシュー(同上)。
Enter強調中なら編集開始("edit-start")、そうでなければ入力欄の値を追加("add")。振り分けはクライアント側の責務。DOM 配線は wasm-full の別イシュー(同上)。
Escape強調を解除する("highlight-clear" dispatch)。DOM 配線は wasm-full の別イシュー(同上)。

WAI-ARIA

AttributeDescription
aria-invalid (input)上限到達(at_max)または props.invalid のときのみ "true" を出力する。
aria-label (item_delete_trigger)"Delete tag {value}"(zag 既定訳)を動的生成する(render() の既定エスケープ経由)。
role="status" / aria-live="polite" / aria-atomic="true" (live_region)タグ数の変化を通知する live region に固定付与する(イシュー #1069)。