インライン編集
inline-edit は最もシンプルな「クリックして編集」パターンです。
表示状態と編集状態は、同じ URL にあるサーバレンダリングされた 2 つの
HTML フラグメントで、htmx が outerHTML で相互にスワップします。
別名: クリックして編集、インプレース編集。
Edit を押して名前を変えて保存してみてください — 空のまま保存すると
エラー状態の 422 編集フラグメントが返り、Cancel で元の値に戻れます。
どの状態もサーバレンダリングされたフラグメントで、契約のデモ実装が
api/recipes/inline-edit/ 配下で提供します(ステートレス: 現在の値は
クエリパラメータで持ち回ります)。実際のアプリでは /items/42/name
など自前の URL を使います。
行セルのデフォルト描画 — 値 + 小さな本物の Edit <button>:
<span id="item-42-name"> Acme widgets <button class="hc-button" data-size="sm" type="button" data-hx-get="/items/42/name/edit" data-hx-target="closest span" data-hx-swap="outerHTML"> Edit </button></span>ボタンこそがアフォーダンスです: キーボードで到達でき(Tab のあと
Enter か Space)、支援技術に読み上げられ、追加の ARIA は不要です。
closest span を outerHTML でターゲットするため、起動すると
表示ノード全体が編集フォームに置き換わります。
<!-- GET /items/42/name/edit --><form id="item-42-name" data-hx-put="/items/42/name" data-hx-target="this" data-hx-swap="outerHTML" style="display: inline-flex; gap: .25rem;"> <input name="name" class="hc-input" data-size="sm" value="Acme widgets" aria-label="Item name" autofocus> <button class="hc-button" data-size="sm" data-variant="primary" type="submit">Save</button> <button class="hc-button" data-size="sm" type="button" data-hx-get="/items/42/name" data-hx-target="closest form" data-hx-swap="outerHTML">Cancel</button></form>Cancel は closest form をターゲットするため、編集フォーム全体が表示
フラグメントへスワップされて戻ります。
ユーザーが入力して Enter(または Save クリック)。htmx がフォームを
PUT /items/42/name へ送信し、サーバが更新後の表示状態を返します:
<!-- PUT /items/42/name --><span id="item-42-name"> Acme widgets v2 <button class="hc-button" data-size="sm" type="button" data-hx-get="/items/42/name/edit" data-hx-target="closest span" data-hx-swap="outerHTML"> Edit </button></span>outerHTML スワップが <form> 全体を表示用の <span> に置き換えます —
同じノード id で、要素型だけが違います。htmx は新しい属性を自動で再処理
します。
バリデーション
Section titled “バリデーション”サーバサイドバリデーションの失敗時は、エラーメッセージをインラインに
載せた編集フォームを 422 で再度返します。outerHTML スワップが
同じノードをターゲットするよう、id は同じに保ちます:
<!-- PUT /items/42/name → 422 --><form id="item-42-name" data-hx-put="/items/42/name" data-hx-target="this" data-hx-swap="outerHTML"> <div class="hc-field" data-invalid="true"> <input class="hc-input" data-size="sm" name="name" value="" aria-label="Item name" aria-invalid="true" aria-describedby="item-42-name-error"> <p id="item-42-name-error" class="hc-field__message">Name is required.</p> </div> <button class="hc-button" data-size="sm" data-variant="primary" type="submit">Save</button> <button class="hc-button" data-size="sm" type="button" data-hx-get="/items/42/name" data-hx-target="closest form" data-hx-swap="outerHTML">Cancel</button></form>htmx ≥ 2 はデフォルトで非 2xx レスポンスをスワップしません。一度だけ グローバルに 422 のスワップを許可します — field-errors レシピが ドキュメント化しているのと同じ許可です:
document.body.addEventListener('htmx:beforeSwap', (event) => { if (event.detail.xhr.status === 422) { event.detail.shouldSwap = true; event.detail.isError = false; }});(代替: エラーフラグメントを 200 で返す、または HX-Reswap /
HX-Retarget ヘッダーを送る。data-hx-target-422="this" も動きますが、
htmx の response-targets 拡張が必要です。)
アクセシビリティ
Section titled “アクセシビリティ”- Edit トリガーは本物の
<button>です — キーボードで到達でき、支援 技術に読み上げられ、追加の ARIA は不要です。(クリック可能な<span>に替える前に、上の注記を確認してください。) - 編集フラグメントには見える
<label>がないので、入力にアクセシブル ネーム(上のaria-label="Item name")を与えてください — ないと スクリーンリーダーのユーザーは名前のないテキストボックスに着地して しまいます。 - キーボードユーザーがすぐ編集フィールドに着地できるよう、入力に
autofocusを使ってください。 - Escape でキャンセルするには、Cancel ボタンをクリックする小さな
keydownリスナーを自分のバンドルに足します(インラインのonkeydownハンドラーは使わないでください — CSP の下で壊れます)。 あるいは Escape 対応を省き、見えるキャンセルボタンに頼ります。
サーバレスポンス契約
Section titled “サーバレスポンス契約”| リクエスト | レスポンス |
|---|---|
GET /items/:id/name | 表示フラグメント(Edit ボタンつきの <span id="item-:id-name">)。 |
GET /items/:id/name/edit | 編集フラグメント(<form id="item-:id-name">…</form>)。 |
PUT /items/:id/name(200) | 新しい値の表示フラグメント。 |
PUT /items/:id/name(422) | data-invalid="true" + .hc-field__message つきの編集フラグメント — 上記の htmx:beforeSwap の許可が必要です。 |
4 つのレスポンスすべてが同じ DOM id を共有します — それがスワップを 可逆にしている当のものです。
プログレッシブエンハンスメント
Section titled “プログレッシブエンハンスメント”- 表示状態はサーバレンダリングされたテキストです — JavaScript が なくても値は読めるままで、クライアント描画に依存するものはページに ありません。
- Edit のアフォーダンスは
data-hx-*属性だけを運ぶtype="button"なので、htmx なしでは不活性です: 押しても何も起きず、どこへも 遷移せず、何も壊れません。その場編集はエンハンスメントです。 - JavaScript なしでも編集可能である必要があるなら、アフォーダンスに
本物の URL を与えてください — フルページの編集フォームへのリンクを
描画し、その POST には
mutating-form の
分岐(
303でページへ戻す)で応答します。上のフラグメント エンドポイントはエンハンスト側の経路として残ります。
- 楽観的 UI? インライン編集では省いてください。ラウンドトリップは 短く、サーバが権威であり、失敗した楽観的更新を巻き戻す帳簿作業は 体感速度の向上に見合いません。
- キーボードの同等性。 Edit ボタンが、表示状態を Tab で到達可能・ Enter で起動可能に保ちます。カスタムトリガーに替えるなら、それを 維持してください。
- 同じフィールドでインライン編集とリモートダイアログを混ぜないで ください。どちらかに絞ります。変更が 1 つの値で、ユーザーがすでに それを見ているならインライン編集が正解。複数フィールドの更新なら リモートダイアログが勝ちます。
- フィールドコンポーネント — バリデーションのスタイリングパターン。
- リモートダイアログレシピ — モーダル内での複数フィールド編集。