コンテンツにスキップ

インライン編集

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 spanouterHTML でターゲットするため、起動すると 表示ノード全体が編集フォームに置き換わります。

<!-- 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 は新しい属性を自動で再処理 します。

サーバサイドバリデーションの失敗時は、エラーメッセージをインラインに 載せた編集フォームを 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 拡張が必要です。)

  • Edit トリガーは本物の <button> です — キーボードで到達でき、支援 技術に読み上げられ、追加の ARIA は不要です。(クリック可能な <span> に替える前に、上の注記を確認してください。)
  • 編集フラグメントには見える <label> がないので、入力にアクセシブル ネーム(上の aria-label="Item name")を与えてください — ないと スクリーンリーダーのユーザーは名前のないテキストボックスに着地して しまいます。
  • キーボードユーザーがすぐ編集フィールドに着地できるよう、入力に autofocus を使ってください。
  • Escape でキャンセルするには、Cancel ボタンをクリックする小さな keydown リスナーを自分のバンドルに足します(インラインの onkeydown ハンドラーは使わないでください — CSP の下で壊れます)。 あるいは Escape 対応を省き、見えるキャンセルボタンに頼ります。
リクエストレスポンス
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 つの値で、ユーザーがすでに それを見ているならインライン編集が正解。複数フィールドの更新なら リモートダイアログが勝ちます。