文字数カウンタ
stimeo--character-counter
残り / 使用文字数を表示し、超過状態をフック化。polite に読み上げる。
stimeo--character-counter コントローラは入力欄の長さを監視し、残り(または使用)文字数を output 要素へ書き込みます。data-near-limit / data-over-limit の状態フックを切り替え、上限を超えると入力欄に aria-invalid を付与し、入力のたびに stimeo--character-counter:change を detail.length / detail.remaining / detail.over とともに発火します。テキスト以外の状態は即座に反映し、表示中のカウント(aria-live="polite" のライブリージョン)は短いデバウンスで書き込むため、連続入力中にスクリーンリーダーへ通知が氾濫しません(入力が止まると確定値を読み上げます)。ライブラリは挙動のみを提供し、カウントの見た目はこの Playground が持ちます。入力リスナとデバウンスタイマは disconnect(Turbo 遷移含む)で破棄し、connect で値を読み直すためキャッシュ復元後もカウントが整合します。
<%# Character counter demo: a textarea whose remaining count is shown beneath it.
The controller writes the count into the output (an aria-live="polite" region),
toggles data-near-limit / data-over-limit on the root, and sets aria-invalid on the
field past the max. This demo styles those state hooks; the library only updates the
count text and the hooks — no consumer JS is needed (it reacts to input on its own). %>
<div class="character-counter-demo">
<label class="character-counter-demo__label" for="cc-message">
<%= t("components.character_counter.demo.label") %>
</label>
<div
class="character-counter"
data-controller="stimeo--character-counter"
data-stimeo--character-counter-max-value="140"
data-stimeo--character-counter-warn-at-value="20">
<textarea
id="cc-message"
class="character-counter__input"
rows="3"
data-stimeo--character-counter-target="input"
aria-describedby="cc-message-count"
placeholder="<%= t('components.character_counter.demo.placeholder') %>"></textarea>
<span
id="cc-message-count"
class="character-counter__output"
data-stimeo--character-counter-target="output"
aria-live="polite"></span>
</div>
</div>
/*
* Presentation-only styles for the character counter demo.
* The library updates the count text and toggles data-near-limit / data-over-limit on
* the root (and aria-invalid on the field); this CSS reacts to those hooks.
*/
.character-counter-demo {
display: flex;
flex-direction: column;
gap: 0.5rem;
max-width: 32rem;
}
.character-counter-demo__label {
font-weight: 600;
color: var(--fg);
}
.character-counter {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.character-counter__input {
width: 100%;
padding: 0.5rem;
border: 1px solid var(--border);
border-radius: 0.375rem;
font: inherit;
resize: vertical;
}
.character-counter__output {
align-self: flex-end;
font-size: 0.85rem;
font-variant-numeric: tabular-nums;
color: var(--color-text-muted);
}
/* Warn as the remaining count gets low, then signal the over-limit state. */
.character-counter[data-near-limit] .character-counter__output {
color: var(--accent);
}
.character-counter[data-over-limit] .character-counter__output {
color: var(--danger-500);
font-weight: 600;
}
.character-counter__input[aria-invalid="true"] {
border-color: var(--danger-500);
}
このデモに固有の消費側 JS はありません(挙動はコントローラが担います)。
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--character-counter"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
input
|
監視対象の入力欄。input/textarea に直接付けた場合はコントローラ要素自身を使う。 |
data-stimeo--character-counter-target="input" |
output
|
カウントを表示する要素。aria-live="polite" のライブリージョンにする。 |
data-stimeo--character-counter-target="output" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
max
|
上限文字数。0 は上限なし(使用数のみ表示)。既定 0。 | data-stimeo--character-counter-max-value |
warnAt
|
残数がこの値以下で data-near-limit を付与。0 で無効。既定 0。 |
data-stimeo--character-counter-warn-at-value |
mode
|
表示モード。remaining / used / both。既定 remaining。 |
data-stimeo--character-counter-mode-value |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
入力のたびに発火。detail.length / detail.remaining / detail.over を伴う。 |
stimeo--character-counter:change |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
data-over-limit |
ルート要素 | 値が max を超えると付与。 |
data-near-limit |
ルート要素 | 残数が warnAt 以下で、まだ超過していないときに付与。 |
aria-invalid |
入力欄 | max を超えている間 "true" を付与。 |
テキスト |
output 要素 | 現在のモードでの残り / 使用文字数(ライブリージョン)。 |