タグ入力
stimeo--tags-input
打った言葉をタグにします。Enter か区切り文字で確定し、削除もできます。
打った言葉をタグに変える入力欄です。Enter か決めた区切り文字で確定します。前後の空白は落とします。空のもの、すでにあるもの、上限を超えたものは受け付けず、その理由を知らせます。同じ言葉を許すこともできます。タグはテンプレートから作られるので見た目は自由で、削除ボタンの名前は言語ごとに書けます。タグの列は Tab で 1 回止まるだけで、その中は左右キーで移動し、Delete で消せます。入力欄が空のときに Backspace を押すと、最後のタグが消えます。確定した一覧は隠しの入力欄へ反映されるので、そのままフォームで送れます。
キーボード操作
| キー | 動作 |
|---|---|
| Enter / 区切り文字 | 入力中の文字列をタグとして確定。 |
| Backspace(空入力) | 入力フォーカスを保って直前のタグを削除。 |
| ← / → | タグ削除ボタン間を移動(並びの末尾の先は入力欄へ戻る)。RTL では左右が入れ替わる。 |
| Delete / Backspace(タグ上) | フォーカス中のタグを削除。 |
<%# Markup for the tags-input demo.
Enter or a comma (delimiter) turns the input value into a tag, blocking duplicates /
empties / over-limit. Tag remove buttons move via roving (left/right keys), and
Backspace on an empty input deletes the previous tag. The library handles tag
create/remove, hidden-field sync, focus handoff, and the add / remove announcements
read by the shared stimeo--announcer your app seats once, in its layout (the tag list
owns no live region of its own). %>
<% announce_added = t("components.tags_input.demo.announce_added") %>
<% announce_removed = t("components.tags_input.demo.announce_removed") %>
<% remove_label = t("components.tags_input.demo.remove_label") %>
<div class="tags-input" data-controller="stimeo--tags-input"
data-stimeo--tags-input-delimiter-value=","
data-stimeo--tags-input-name-value="frameworks[]"
data-stimeo--tags-input-announce-text-value="<%= announce_added %>"
data-stimeo--tags-input-announce-removed-text-value="<%= announce_removed %>">
<ul
class="tags-input__tags"
role="list"
aria-label="<%= t("components.tags_input.demo.tags_label") %>"
data-stimeo--tags-input-target="tags"></ul>
<input
type="text"
class="tags-input__input"
aria-label="<%= t("components.tags_input.demo.input_label") %>"
aria-describedby="tags-input-help"
data-stimeo--tags-input-target="input"
data-action="keydown->stimeo--tags-input#onKeydown" />
<span id="tags-input-help" class="tags-input__help">
<%= t("components.tags_input.demo.help") %>
</span>
<%# The library creates and syncs the submit hidden inputs as name="frameworks[]". %>
<div hidden data-stimeo--tags-input-target="fields"></div>
<template data-stimeo--tags-input-target="tagTemplate">
<li class="tags-input__tag" role="listitem" data-stimeo--tags-input-target="tag">
<span data-stimeo--tags-input-target="label"></span>
<%# Removal is handled by a delegated listener on the tags container, so it
works instantly without waiting on Stimulus wiring a data-action onto the
dynamically-added button. %>
<button
type="button"
class="tags-input__remove"
tabindex="-1"
aria-label="<%= remove_label %>"
data-stimeo--tags-input-target="remove">×</button>
</li>
</template>
</div>
/*
* Presentation-only styles for the tags-input demo.
* The library handles creating/removing tags, syncing the hidden field, and
* roving (tabindex). data-stimeo--tags-input-full (set when max is reached) is
* the hook used here to dim the input.
*/
.tags-input {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.4rem;
max-width: 28rem;
padding: 0.4rem;
border: 1px solid var(--border-strong);
border-radius: 0.5rem;
background: var(--surface, var(--surface-card));
}
.tags-input__tags {
display: contents;
margin: 0;
padding: 0;
list-style: none;
}
.tags-input__tag {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.15rem 0.5rem;
border-radius: 999px;
background: var(--vital-100);
color: var(--vital-800);
font-size: 0.8125rem;
}
.tags-input__remove {
display: inline-flex;
border: 0;
background: transparent;
color: inherit;
font: inherit;
line-height: 1;
cursor: pointer;
}
.tags-input__remove:focus-visible {
outline: 2px solid var(--accent, var(--color-primary));
outline-offset: 2px;
border-radius: 50%;
}
.tags-input__input {
flex: 1 1 8rem;
min-width: 8rem;
border: 0;
background: transparent;
font: inherit;
color: var(--fg, var(--color-text));
}
.tags-input__input:focus {
outline: none;
}
/* The tag list and its input read as one control, so the ring frames the whole box
rather than the bare input inside it. */
.tags-input:focus-within {
outline: 2px solid var(--accent);
outline-offset: 1px;
}
.tags-input__help {
flex-basis: 100%;
font-size: 0.75rem;
color: var(--color-text-muted);
}
.tags-input[data-stimeo--tags-input-full] .tags-input__input {
opacity: 0.5;
}
このデモに固有の消費側 JS はありません(挙動はコントローラが担います)。
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--tags-input"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
input
必須
|
タグを入力し確定する前のテキスト欄。 | data-stimeo--tags-input-target="input" |
tags
必須
|
チップ一覧のコンテナ。削除・ナビをここで委譲処理する。 | data-stimeo--tags-input-target="tags" |
tag
|
描画された個々のチップ。data-value にタグ値を持つ。 |
data-stimeo--tags-input-target="tag" |
tagTemplate
必須
|
各チップを生成する複製元の template。 | data-stimeo--tags-input-target="tagTemplate" |
label
|
チップの表示ラベルを書き込む template 内の要素。 | data-stimeo--tags-input-target="label" |
remove
|
チップ削除ボタン。利用側が記述した aria-label の {label} / {value} を展開する。 |
data-stimeo--tags-input-target="remove" |
fields
|
タグ集合を name 付き隠し入力として送信用に複製するコンテナ。 | data-stimeo--tags-input-target="fields" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
delimiter
|
Enter と並び、入力をタグ確定するキー(既定はカンマ)。 | data-stimeo--tags-input-delimiter-value |
max
|
タグ数の上限。0(既定)で無制限。 | data-stimeo--tags-input-max-value |
allowDuplicates
|
true で重複タグを許可する(既定 false)。 | data-stimeo--tags-input-allow-duplicates-value |
name
|
生成する隠し入力の name(既定 tags[])。 | data-stimeo--tags-input-name-value |
announceText
|
追加時に共有 Announcer へ送る文言。{label} / {value} / {count} を展開。 |
data-stimeo--tags-input-announce-text-value |
announceRemovedText
|
削除時に共有 Announcer へ送る文言。同じプレースホルダーを展開。 | data-stimeo--tags-input-announce-removed-text-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
onKeydown
|
Enter/区切りで確定、空欄 Backspace で入力フォーカスを保って末尾削除、ArrowLeft でチップ一覧へ移る。 | stimeo--tags-input#onKeydown |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
利用者によるタグ追加・削除時に発火。detail に tags 配列を載せる。 | stimeo--tags-input:change |
reconcile
|
外部 DOM / Turbo の変更でタグ順序が変わったとき発火。detail は change と同じ。 | stimeo--tags-input:reconcile |
reject
|
追加が拒否されると発火。detail に値と理由(empty/duplicate/max)を載せる。 | stimeo--tags-input:reject |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
tabindex |
タグ削除ボタン | ロービング: アクティブ=0 / 他=-1(タグ列は 1 タブストップ)。 |
data-stimeo--tags-input-full |
コントローラ要素 | max 到達時に付与(入力抑止の手掛かり)。 |