フィールドエラー
field-errors は、htmx 越しのサーバサイドバリデーションエラーの
ためのキットの契約です。サーバは失敗した送信(通常 422)に、問題の
フィールドを名指しする小さなアラートフラグメントで答えます。
installFieldErrors() ビヘイビアが各エラーを一致するフィールドへ
振り分け — メッセージはフィールドのエラースロットへ、コントロールには
aria-invalid + aria-describedby — ユーザーが直すたびにクリア
します。フラグメントはドキュメント化されたワイヤ形式なので、
テンプレートエンジンやコードジェネレーターはそのまま出力できます。
別名: サーバーバリデーション表示、インラインエラー。
メールを空のまま送信すると required エラーが、taken@example.com を
使うと duplicate の項目がフィールドへ振り分けられます(メッセージ、
aria-invalid、フォーカス)。それ以外のメールは成功します: サーバは
エラーをクリアする空ボディと HX-Trigger トーストで応答します。
エンドポイントは下記サーバ契約のデモ実装で、api/recipes/field-errors/
配下に置かれています(実際のアプリでは /members など自前の URL を
使います)。
フラグメント
Section titled “フラグメント”サーバが返す正準の形はこれです。htmx がフォーム内(またはフォームを 指す)コンテナへスワップします:
<div class="hc-alert" data-variant="error" role="alert" data-hc-field-errors> <p class="hc-alert__title">Please fix the errors below.</p> <ul class="hc-alert__errors"> <li class="hc-alert__error" data-field="email" data-code="duplicate" data-message-key="members.email.duplicate">email: duplicate</li> </ul> <p class="hc-alert__body">optional hint line</p></div>data-hc-field-errorsがビヘイビアのオプトインです。空なら 最寄りの<form>へ振り分け。値があればフォームを指す CSS セレクタ (out-of-band スワップや、フォーム外に描画されるアラート用)。data-fieldがコントロール(そのname属性)を名指しします。 ラジオ / チェックボックスグループは共有フィールドに解決され、隠し input はスキップされます — 真偽値フィールドのイディオム (同名の 隠しfalse+ チェックボックスtrue)は見える チェックボックスに配線されます。既知のコントロールを名指ししない 項目はサマリーに見えたまま残ります — フォームレベルのエラーに 使ってください。data-message-keyは任意のクライアントサイドローカライズ (後述)。項目のテキストがフォールバックです。data-codeは スタイリングやテストのためにそのまま通されます。
タイトルと項目のテキストはアプリが選ぶコピーです — 言い回しは UI コピーの書き方を 参照してください。
Simulate 422 をクリックすると、まさに上のフラグメントがフォームの 隣に挿入されます(htmx スワップの代役)。email のエラーは完全な ARIA 配線つきでフィールドのインラインに着地し、未知フィールドの項目は サマリーに残ります。email フィールドを編集するとエラーがクリアされ ます。
<form data-hx-post="/members" data-hx-target="#form-errors" data-hx-swap="innerHTML"> <div id="form-errors"></div>
<div class="hc-field"> <label class="hc-field__label" for="email">Email</label> <input class="hc-input" id="email" name="email" type="email"> </div>
<button type="submit" class="hc-button" data-variant="primary">Save</button></form>何が起きるか
Section titled “何が起きるか”- htmx がフラグメントをスワップし、ビヘイビアが
htmx:afterSwap/htmx:oobAfterSwapに反応します (MutationObserverフォールバックと、フルページのエラー描画向けの インストール時スキャンつき)。 - フォーム内の以前のサーバエラーがクリアされます。
- コントロールに一致する各
.hc-alert__error[data-field]は、 メッセージをフィールドの.hc-field__error(自動作成 —hc-fieldラッパーのない素のコントロールの後にも)へ書き込み、 コントロールにaria-invalid="true"とaria-describedby、 フィールドにdata-invalid="true"を設定し、data-distributed="true"の印が付きます(二重に読まれないよう サマリーでは非表示)。 - アラートに
data-distributed="all | partial | none"が刻印され、 最初の無効なコントロールにフォーカスが移ります。 - フィールドのサーバエラーは、そのフィールドでの最初の
input/change、submit / reset、および次のフラグメントの振り分け 前にクリアされます。
フラグメントの属性
Section titled “フラグメントの属性”| 属性 | 対象 | 意味 |
|---|---|---|
data-hc-field-errors | アラート | オプトイン。空 = closest('form')、値 = フォームを指す CSS セレクタ。 |
data-field | 項目 | エラーを付けるコントロールの name。 |
data-code | 項目 | 任意の機械可読コード(リゾルバから {code} として利用可)。 |
data-message-key | 項目 | 任意の i18n カタログキー。項目テキストがフォールバック。 |
data-message-params | 項目 | カタログ検索の補間値の JSON オブジェクト(任意)。暗黙の {field}/{code} の上にマージ。不正な JSON は無視。 |
data-summary="auto" | アラート | すべての項目が振り分けられたらアラート全体を隠す。 |
data-focus="none" | アラート | 最初の無効なコントロールへフォーカスしない。 |
メッセージキーのローカライズ
Section titled “メッセージキーのローカライズ”data-message-key は、他のすべてのキット文字列と同じ
i18n カタログで解決され
ます — 起動時に一度設定してください:
import { setMessages } from '@hypermedia-components/core';
setMessages({ 'members.email.duplicate': 'このメールアドレスは既に登録されています', // `{field}` / `{code}` interpolate if you want generic messages: 'errors.required': '{field} is required',});翻訳が {field}/{code} 以外の値 — 制約パラメータ、バリデーション行の
カラム — を必要とするときは、サーバが data-message-params に JSON
オブジェクトとして送り、クライアントのリゾルバが補間パラメータへ
マージします(項目の値が暗黙のものに勝ちます):
<li class="hc-alert__error" data-field="qty" data-code="stock" data-message-key="orders.qty.exceeds" data-message-params='{"stock": 5}'>在庫 5 を超えています。</li>setMessages({ 'orders.qty.exceeds': '在庫 {stock} を超えています。' });カタログにないキーは項目自身のテキストへフォールバックします — サーバが
ローカライズを始めたら、最終テキストを出力してキーを落とせばよく、
クライアント側は何も変わりません。不正な data-message-params JSON も
同様に無視され、項目テキストがそのまま描画されます。
サーバレスポンス契約
Section titled “サーバレスポンス契約”htmx ≥ 2 はデフォルトで非 2xx をスワップしません。一度だけグローバルに
422 のスワップを許可します:
document.body.addEventListener('htmx:beforeSwap', (event) => { if (event.detail.xhr.status === 422) { event.detail.shouldSwap = true; event.detail.isError = false; }});代わりにフラグメントを 200 で返しても構いません — クライアント側の
設定が一切要らない唯一の選択肢です。HX-Retarget: #form-errors +
HX-Reswap: innerHTML はフラグメントの行き先と方法を操縦するだけで、
htmx ≥ 2 ではスワップを許可しません。これらを付けた 422 にも上の許可
(もしくは htmx.config.responseHandling のルール)が必要です。
クライアントサイドバリデーションとの関係
Section titled “クライアントサイドバリデーションとの関係”installValidation()は、
ネイティブ制約エラーを同じ .hc-field__error スロットへ、同じ ARIA
配線で表出させます。両者は合成します: 同じコントロール上では
ネイティブメッセージがサーバエラーより優先され(現在の値を反映して
いるため)、どちらも相手の状態を早まってクリアしません。
アクセシビリティ
Section titled “アクセシビリティ”- サマリーは
role="alert"を使うため、スワップ自体が読み上げられ ます。フィールドメッセージはaria-live="polite"で、コントロール からaria-describedbyで参照されます。 - 最初の無効なコントロールにフォーカスが移ります — 送信後エラーの
標準パターンです。フローが自前でフォーカスを管理するなら
data-focus="none"で無効化してください。 - 振り分けられた項目はサマリーから視覚的に除かれるだけです。どの フィールドにも一致しない項目は見えたまま残るため、エラーが静かに 落ちることはありません。
プログレッシブエンハンスメント
Section titled “プログレッシブエンハンスメント”- JavaScript なし(フルページ再描画)では、アラートがすべてのエラーを 素のリストとして描画します — 振り分けはエンハンスメントで、何も 失われません。
- htmx なしでも、初期 HTML に存在するフラグメントはインストール時 スキャンによって振り分けられます。