フォームフィールド
stimeo--form-field
コントロールと説明・エラーを aria-describedby / aria-invalid / aria-errormessage で関連付ける。
stimeo--form-field コントローラは、フォームコントロールの関連付けを肩代わりする基盤です。説明・エラー要素に id を採番してコントロールの aria-describedby を合成し、aria-invalid を切り替え、aria-errormessage をライブなエラー領域へ向けます。setError(アクションパラメータ経由)と clearError でエラー状態を制御し、 stimeo--form-field:validate を発火します。バリデーション自体は行わず、見た目も持ちません(この Playground 側が担います)。
確認メールを送信します。
このデモはフィールドのエラー状態(このコントローラが担う ARIA の関連付け)を切り替えます。入力値の検証や値のクリアは行いません。「エラーを表示」でフィールドを無効状態にして role="alert" 領域でメッセージを読み上げ、「エラーを解除」で正常状態に戻します。
<%# Markup for the form-field demo.
Associates label / description / error with the control via aria-describedby /
aria-invalid / aria-errormessage. Showing/clearing the error uses the action
parameter (message) and #clearError. The look (e.g. the invalid border) switches
on data-stimeo--form-field-invalid and aria-invalid. %>
<div class="form-field-demo" data-controller="stimeo--form-field"
data-stimeo--form-field-focus-on-error-value="true">
<label class="form-field-demo__label" for="ff-email">
<%= t("components.form_field.demo.label") %>
</label>
<input class="form-field-demo__control" id="ff-email" type="email" aria-invalid="false"
placeholder="<%= t('components.form_field.demo.placeholder') %>"
data-stimeo--form-field-target="control" />
<%# Helper description. On connect it's assigned an id and linked into the
control's aria-describedby. %>
<p class="form-field-demo__description" data-stimeo--form-field-target="description">
<%= t("components.form_field.demo.hint") %>
</p>
<%# Note clarifying the demo's intent (this part only wires up the error state;
it doesn't validate or clear the value). %>
<p class="form-field-demo__note"><%= t("components.form_field.demo.note") %></p>
<%# Error region. role="alert" so it's announced without moving focus. Starts hidden. %>
<p class="form-field-demo__error" role="alert" hidden
data-stimeo--form-field-target="error"></p>
<div class="form-field-demo__actions">
<%# setError reads the message from the action parameter (no consumer JS needed). %>
<button type="button" class="demo-trigger"
data-stimeo--form-field-message-param="<%= t('components.form_field.demo.error') %>"
data-action="click->stimeo--form-field#setError">
<%= t("components.form_field.demo.validate") %>
</button>
<button type="button" class="demo-trigger"
data-action="click->stimeo--form-field#clearError">
<%= t("components.form_field.demo.clear") %>
</button>
</div>
<%# Helper region that subscribes to the validate event to show status (updated by demo.js). %>
<p class="form-field-demo__status" id="ff-status" aria-live="polite"
data-valid-text="<%= t('components.form_field.demo.valid') %>"></p>
</div>
/*
* Presentation-only styles for the form-field demo.
* The invalid state is expressed via data-stimeo--form-field-invalid (root) and
* aria-invalid (control).
*/
.form-field-demo {
display: flex;
flex-direction: column;
gap: 0.4rem;
max-width: 22rem;
}
.form-field-demo__label {
font-weight: 600;
}
.form-field-demo__control {
padding: 0.5rem 0.75rem;
font-size: 1rem;
color: var(--fg);
background: var(--bg);
border: 1px solid var(--border-interactive);
border-radius: 0.375rem;
transition: border-color 0.15s ease;
}
.form-field-demo__control:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* Show the invalid state (aria-invalid) with an error-colored border. */
.form-field-demo__control[aria-invalid="true"] {
border-color: var(--danger-500);
}
.form-field-demo__description {
margin: 0;
font-size: 0.85rem;
color: var(--color-text-muted);
}
/* Note conveying the demo's intent. Shown in a more muted tone than the description. */
.form-field-demo__note {
margin: 0;
font-size: 0.8rem;
line-height: 1.5;
color: var(--color-text-muted);
border-left: 2px solid var(--border-strong);
padding-left: 0.6rem;
}
.form-field-demo__error {
margin: 0;
font-size: 0.85rem;
font-weight: 600;
color: var(--danger-500);
}
.form-field-demo__actions {
display: flex;
gap: 0.5rem;
margin-top: 0.25rem;
}
.form-field-demo__status {
margin: 0;
min-height: 1.25rem;
font-size: 0.85rem;
color: var(--leaf-500);
}
// Demo script that subscribes to the form-field validate event and updates the status text.
// The error message itself is consolidated in the role="alert" region; the lower status
// is shown only on success.
// Listen on the demo root (the controller element the event is dispatched on / bubbles
// through), not document: a document listener survives Turbo body swaps and stacks up on
// every navigate-away→back, whereas a root-scoped listener is torn down with the body.
const root = document.querySelector('.form-field-demo');
root?.addEventListener('stimeo--form-field:validate', function (event) {
const status = document.getElementById('ff-status');
if (!status) return;
if (event.detail.valid) {
status.textContent = status.dataset.validText || '';
status.style.color = '#16a34a';
} else {
status.textContent = '';
}
});
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--form-field"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
control
必須
|
aria-invalid・aria-describedby・aria-errormessage が紐付けられるフォームコントロール。 |
data-stimeo--form-field-target="control" |
description
|
id を自動付与され、コントロールの aria-describedby に組み込まれる補助テキスト要素。 |
data-stimeo--form-field-target="description" |
error
|
表示/非表示が切り替わり aria-errormessage で参照される live なエラー領域(理想は role=alert)。 |
data-stimeo--form-field-target="error" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
focusOnError
|
エラー設定時にコントロールへフォーカスを移すか(既定 false)。 | data-stimeo--form-field-focus-on-error-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
clearError
|
全エラー領域を空にして非表示にし、フィールドを有効状態にする。 | stimeo--form-field#clearError |
setError
|
フィールドを無効状態にしエラーメッセージを表示する(文字列またはアクションの message パラメータから取得)。 |
stimeo--form-field#setError |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
validate
|
setError/clearError 時に発火。detail は { valid, message }。 |
stimeo--form-field:validate |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
aria-describedby |
コントロール | 説明と表示中エラーの id を連結した値。 |
aria-invalid |
コントロール | エラー表示中は "true"、それ以外は "false"。 |
aria-errormessage |
コントロール | 表示中のエラー領域を指す。正常時は削除。 |
hidden |
エラー | エラーが無いとき付与、表示時に削除。 |
data-stimeo--form-field-invalid |
ルート | 無効状態の間だけ付与(CSS 用フック)。 |