フォームフィールド
stimeo--form-field
入力欄と、その説明やエラーを結びつけます。読み上げに正しい順で届きます。
入力欄と、その説明文やエラー文を結びつける土台です。説明とエラーには自動で目印が振られ、画面に並んでいる順のまま読み上げに届きます。エラーになった入力欄は、不正であることも読み上げに伝わります。エラーの設定と解除はマークアップから行え、そのつどイベントが出ます。エラーを出したときは、共通の窓口から読み上げられます。検証そのものは行わず、見た目も持ちません。
確認メールを送信します。
このデモはフィールドのエラー状態(このコントローラが担う ARIA の関連付け)を切り替えます。入力値の検証や値のクリアは行いません。「エラーを表示」でフィールドを無効状態にして共通の assertive Announcer へ文言を渡し、「エラーを解除」で正常状態に戻します。
キーボード操作
このコンポーネント自体はキーボード操作を持ちません。
<%# 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 error text is spoken assertively by the
shared stimeo--announcer your app seats once, in its layout — a region that stands
in the accessibility tree ahead of the first error, which is what makes the message
observable. The look 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. It is 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>
<%# Visual error region. Starts hidden; speech is delegated to the shared announcer. %>
<p class="form-field-demo__error" 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 and shows the valid
state only (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 is read by the shared announcer, so this region shows the valid state
// only and never duplicates that wording.
// 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 で参照される視覚エラー。読み上げは共通 Announcer が担う。 |
data-stimeo--form-field-target="error" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
focusOnError
|
エラー設定時にコントロールへフォーカスを移すか(既定 false)。 | data-stimeo--form-field-focus-on-error-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
clearError
|
全エラー領域を空にして非表示にし、フィールドを有効状態にする。 | stimeo--form-field#clearError |
setError
|
フィールドを無効状態にしエラーメッセージを表示する。プログラム呼び出しでは { focus } でその回だけ focusOnError を上書きできる。 |
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 用フック)。 |