T-CREATOR

htmx 属性チートシート:hx-get/hx-post/hx-swap/hx-target 早見表【実例付き】

htmx 属性チートシート:hx-get/hx-post/hx-swap/hx-target 早見表【実例付き】

htmx を使い始めると、いくつかの重要な属性に何度も触れることになります。特に hx-gethx-posthx-swaphx-target の 4 つは、htmx の核となる機能を担っているため、これらを理解することが開発のスピードと品質を大きく左右します。

この記事では、htmx の代表的な 4 つの属性に焦点を当て、それぞれの役割や使い方を実例とともに解説していきます。チートシートとして活用できるよう、各属性の基本から応用まで、わかりやすく整理しました。

クイックリファレンス:4 大属性早見表

まず、すぐに使える早見表から確認していきましょう。

hx-get / hx-post:HTTP リクエスト送信

#属性用途基本構文主な使用例
1hx-getデータ取得hx-get="​/​api​/​path"一覧表示、検索、詳細取得
2hx-postデータ送信hx-post="​/​api​/​path"フォーム送信、登録、更新
3hx-putデータ更新hx-put="​/​api​/​path"リソース全体の更新
4hx-patch部分更新hx-patch="​/​api​/​path"リソースの一部を更新
5hx-deleteデータ削除hx-delete="​/​api​/​path"リソース削除

よく使う組み合わせ

html<!-- データ取得 -->
<button hx-get="/api/users">ユーザー一覧</button>

<!-- フォーム送信 -->
<form hx-post="/api/users">...</form>

<!-- 削除 -->
<button
  hx-delete="/api/users/123"
  hx-confirm="削除しますか?"
>
  削除
</button>

hx-swap:DOM 挿入モード早見表

#モード動作使用シーンコード例
1innerHTML内容を置換(デフォルト)コンテンツ更新hx-swap="innerHTML"
2outerHTML要素ごと置換要素全体の入れ替えhx-swap="outerHTML"
3beforebegin要素の前に挿入リスト先頭追加(外側)hx-swap="beforebegin"
4afterbegin最初の子として挿入リスト先頭追加(内側)hx-swap="afterbegin"
5beforeend最後の子として挿入リスト末尾追加(内側)hx-swap="beforeend"
6afterend要素の後に挿入リスト末尾追加(外側)hx-swap="afterend"
7delete要素を削除削除操作hx-swap="delete"
8noneDOM 変更なしサイドエフェクトのみhx-swap="none"

視覚的な挿入位置イメージ

bashbeforebegin
<div id="target">
  afterbegin
  既存コンテンツ
  beforeend
</div>
afterend

実例パターン

html<!-- リストの末尾に追加 -->
<ul id="list" hx-swap="beforeend">
  <li>既存項目</li>
</ul>

<!-- 要素全体を置き換え -->
<div hx-get="/new" hx-swap="outerHTML">古い内容</div>

<!-- 削除 -->
<button hx-delete="/api/item/1" hx-swap="outerHTML">
  削除
</button>

hx-target:挿入先指定早見表

#指定方法説明構文例使用場面
1CSS IDID 指定hx-target="#result"固定の結果エリア
2CSS classクラス指定hx-target=".content"複数候補から最初の要素
3this自分自身(デフォルト)hx-target="this"ボタン自体を更新
4closest最も近い祖先hx-target="closest .card"親要素を探す
5find子孫要素hx-target="find .result"子要素を探す
6next次の兄弟hx-target="next div"すぐ下の要素
7previous前の兄弟hx-target="previous div"すぐ上の要素
8document.bodybody 要素hx-target="body"ページ全体を置換

実例パターン

html<!-- 固定エリアに結果表示 -->
<button hx-get="/data" hx-target="#result">取得</button>
<div id="result"></div>

<!-- 親カードを更新 -->
<div class="card">
  <button hx-get="/edit" hx-target="closest .card">
    編集
  </button>
</div>

<!-- 次の要素に挿入 -->
<button hx-get="/details" hx-target="next .details">
  詳細表示
</button>
<div class="details"></div>

よく使う組み合わせパターン

#パターン属性の組み合わせ使用例
1リスト追加hx-post + hx-target + hx-swap="beforeend"タスク追加、コメント追加
2インライン編集hx-get + hx-target="closest" + hx-swap="outerHTML"ユーザー情報編集
3検索結果表示hx-get + hx-trigger="keyup" + hx-targetリアルタイム検索
4削除hx-delete + hx-confirm + hx-swap="outerHTML"アイテム削除
5ページングhx-get + hx-target + hx-swap="beforeend"無限スクロール

実例:リスト追加

html<form
  hx-post="/api/tasks"
  hx-target="#task-list"
  hx-swap="beforeend"
>
  <input name="task" placeholder="新しいタスク" />
  <button type="submit">追加</button>
</form>
<ul id="task-list">
  <li>既存タスク</li>
</ul>

実例:インライン編集

html<div class="user-card">
  <p>山田太郎</p>
  <button
    hx-get="/edit/123"
    hx-target="closest .user-card"
    hx-swap="outerHTML"
  >
    編集
  </button>
</div>

実例:リアルタイム検索

html<input
  type="text"
  hx-get="/search"
  hx-trigger="keyup changed delay:300ms"
  hx-target="#results"
  placeholder="検索..."
/>
<div id="results"></div>

補助的な属性クイックリファレンス

#属性役割使用例
1hx-triggerイベント指定hx-trigger="click"
2hx-confirm確認ダイアログhx-confirm="本当に削除?"
3hx-indicatorローディング表示hx-indicator="#spinner"
4hx-params送信パラメータ制御hx-params="name,email"
5hx-vals追加データ送信hx-vals='{"key":"value"}'
6hx-headersカスタムヘッダーhx-headers='{"X-Token":"abc"}'
7hx-include追加要素の値も送信hx-include="#other-form"
8hx-push-urlURL 履歴に追加hx-push-url="true"

トラブルシューティング早見表

#問題原因解決方法
1レスポンスが表示されないhx-targetの指定ミスセレクタが正しいか確認
2要素が重複するswapモードが不適切innerHTMLまたはouterHTMLに変更
3リクエストが送信されないトリガーイベント未発生hx-triggerを確認
4古いコンテンツが残るswapモードがbeforeendinnerHTMLに変更
5スタイルが反映されないCSS 未読み込みインラインスタイルまたは head に CSS
6フォームが二重送信されるhx-postと通常の submit 併存type="button"に変更

この早見表を手元に置いておくと、htmx の開発がスムーズに進みます。次のセクションから、各属性の詳細な使い方を見ていきましょう。

背景

htmx が解決する課題

従来の Web アプリケーション開発では、動的なコンテンツ更新のために JavaScript フレームワークを導入する必要がありました。しかし、htmx は HTML 属性だけで AJAX リクエストや DOM 更新を実現できる革新的なライブラリです。

htmx の基本的な動作フローを図で確認してみましょう。

mermaidflowchart LR
  user["ユーザー"] -->|クリック/操作| element["HTML要素<br/>(hx-*属性付き)"]
  element -->|HTTPリクエスト| server["サーバー"]
  server -->|HTMLフラグメント| element
  element -->|DOM更新| page["ページ"]
  page -->|表示| user

図で理解できる要点

  • ユーザーの操作が HTML 要素のイベントをトリガーする
  • htmx 属性により自動的にサーバーへリクエストが送信される
  • サーバーから返された HTML が指定された場所に挿入される

htmx の主要属性の位置づけ

htmx には多数の属性がありますが、以下の 4 つは特に使用頻度が高く、基本的な機能を支えています。

#属性役割使用頻度
1hx-getGET リクエストを送信★★★★★
2hx-postPOST リクエストを送信★★★★★
3hx-swapレスポンスの挿入方法を指定★★★★☆
4hx-targetレスポンスの挿入先を指定★★★★☆

これらの属性を組み合わせることで、ほとんどの動的 UI パターンを実装できます。

課題

JavaScript フレームワークの複雑さ

React や Vue.js などのフレームワークは強力ですが、以下のような課題があります。

  • ビルドプロセスやツールチェーンの設定が必要
  • 状態管理の仕組みを理解する必要がある
  • バンドルサイズが大きくなりがち
  • サーバーサイドとクライアントサイドで異なる言語・パラダイムを扱う必要がある

以下の図は、従来の SPA 開発と htmx を使った開発の複雑さの違いを示しています。

mermaidflowchart TB
  subgraph spa["従来のSPA開発"]
    direction TB
    spa1["フロントエンド<br/>(React/Vue)"] -->|JSON API| spa2["バックエンド"]
    spa1 -->|状態管理| spa3["Redux/Vuex"]
    spa1 -->|ビルド| spa4["Webpack/Vite"]
  end

  subgraph htmx_dev["htmx開発"]
    direction TB
    htmx1["HTML + htmx属性"] -->|HTMLフラグメント| htmx2["サーバー"]
  end

図で理解できる要点

  • SPA 開発は複数のレイヤーと技術スタックが必要
  • htmx 開発はシンプルな HTML とサーバーの往復のみ
  • 学習コストと保守コストに大きな差がある

動的コンテンツ更新の煩雑さ

JavaScript を使わずに動的なコンテンツ更新を実現するには、以下のような手間が発生します。

  • XMLHttpRequestfetch API を手動で実装
  • DOM 操作のコードを記述
  • イベントリスナーの設定と管理
  • エラーハンドリングの実装

これらの作業は同じようなコードの繰り返しになりがちで、開発効率を低下させる原因となっています。

解決策

htmx の 4 大属性による宣言的な UI 更新

htmx は HTML 属性を使って宣言的に動作を定義できます。これにより、JavaScript を書かずに動的な UI を実現できるのです。

4 つの主要属性の関係性を図で確認しましょう。

mermaidflowchart TD
  trigger["イベント発生<br/>(クリック等)"] --> method{HTTPメソッド}
  method -->|GET| hxget["hx-get<br/>データ取得"]
  method -->|POST| hxpost["hx-post<br/>データ送信"]

  hxget --> response["サーバーレスポンス<br/>(HTMLフラグメント)"]
  hxpost --> response

  response --> target["hx-target<br/>挿入先要素を指定"]
  target --> swap["hx-swap<br/>挿入方法を指定"]

  swap --> result["DOM更新完了"]

図で理解できる要点

  • hx-get / hx-post でリクエスト方法を決定
  • hx-target で HTML を挿入する場所を指定
  • hx-swap で挿入の仕方(置換・追加等)を制御

次のセクションから、各属性の詳細と実例を見ていきます。

具体例

hx-get:GET リクエストでデータを取得

基本的な使い方

hx-get 属性は、指定した URL に GET リクエストを送信し、レスポンスを要素に挿入します。

html<button hx-get="/api/users">ユーザー一覧を読み込む</button>

このボタンをクリックすると、​/​api​/​users に GET リクエストが送信され、返ってきた HTML がボタン要素の中身として置き換わります。

トリガーイベントの指定

デフォルトではボタンは click イベント、入力フォームは change イベントでリクエストが送信されます。hx-trigger 属性で変更できます。

html<input
  type="text"
  name="search"
  hx-get="/api/search"
  hx-trigger="keyup changed delay:500ms"
  placeholder="検索キーワードを入力"
/>

上記のコードでは、キー入力の 500ms 後に自動的に検索リクエストが送信されます。ユーザーが入力を続けている間は無駄なリクエストが発生しません。

クエリパラメータの送信

フォーム要素の値は自動的にクエリパラメータとして送信されます。

html<form hx-get="/api/filter">
  <select
    name="category"
    hx-get="/api/filter"
    hx-trigger="change"
  >
    <option value="all">すべて</option>
    <option value="tech">技術</option>
    <option value="design">デザイン</option>
  </select>
</form>

選択を変更すると ​/​api​/​filter?category=tech のような URL にリクエストが送信されます。

サーバー側のレスポンス例

サーバーは HTML フラグメント(部分的な HTML)を返します。完全な HTML ドキュメントではなく、必要な部分だけを返すのがポイントです。

javascript// Express.js の例
app.get('/api/users', (req, res) => {
  const users = getUsersFromDB();

  // HTML フラグメントを返す
  const html = users
    .map(
      (user) =>
        `<div class="user-card">
      <h3>${user.name}</h3>
      <p>${user.email}</p>
    </div>`
    )
    .join('');

  res.send(html);
});

このコードでは、データベースから取得したユーザー情報を HTML として整形して返しています。JSON ではなく HTML を返すことで、クライアント側での描画処理が不要になります。

hx-post:POST リクエストでデータを送信

基本的な使い方

hx-post 属性は、フォームデータを POST リクエストで送信します。

html<form hx-post="/api/users">
  <input type="text" name="name" placeholder="名前" />
  <input
    type="email"
    name="email"
    placeholder="メールアドレス"
  />
  <button type="submit">登録</button>
</form>

フォームを送信すると、入力された値が ​/​api​/​users に POST リクエストとして送信されます。

JSON データの送信

デフォルトでは application​/​x-www-form-urlencoded 形式でデータが送信されますが、hx-ext="json-enc" を使うと JSON 形式で送信できます。

html<script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>

<form hx-post="/api/users" hx-ext="json-enc">
  <input type="text" name="name" placeholder="名前" />
  <input
    type="email"
    name="email"
    placeholder="メールアドレス"
  />
  <button type="submit">登録</button>
</form>

まず、json-enc 拡張機能を読み込みます。これにより、フォームデータが JSON 形式でサーバーに送信されるようになります。

サーバー側のレスポンス例

POST リクエストの処理後、成功メッセージやエラーメッセージを HTML で返します。

javascript// Express.js の例
app.post('/api/users', (req, res) => {
  const { name, email } = req.body;

  try {
    // データベースに保存
    saveUserToDB({ name, email });

    // 成功メッセージを HTML で返す
    res.send(`
      <div class="success-message">
        <p>✓ ${name} さんを登録しました</p>
      </div>
    `);
  } catch (error) {
    // エラーメッセージを HTML で返す
    res.status(400).send(`
      <div class="error-message">
        <p>✗ 登録に失敗しました: ${error.message}</p>
      </div>
    `);
  }
});

このコードでは、データの保存処理を実行した後、結果に応じた HTML を返しています。クライアント側では特別な処理をせずとも、適切なメッセージが表示されます。

確認ダイアログの追加

重要な操作の前に確認ダイアログを表示できます。

html<button
  hx-post="/api/users/123/delete"
  hx-confirm="本当に削除しますか?"
>
  ユーザーを削除
</button>

hx-confirm 属性を使うと、リクエスト送信前にブラウザの確認ダイアログが表示されます。

hx-swap:レスポンスの挿入方法を制御

swap モードの種類

hx-swap 属性は、サーバーから返された HTML をどのように DOM に挿入するかを指定します。

#モード説明使用シーン
1innerHTML要素の内容を置き換え(デフォルト)コンテンツの更新
2outerHTML要素自体を置き換え要素全体の入れ替え
3beforebegin要素の直前に挿入リストの先頭追加
4afterbegin要素の最初の子として挿入リストの先頭追加(内側)
5beforeend要素の最後の子として挿入リストの末尾追加(内側)
6afterend要素の直後に挿入リストの末尾追加
7delete要素を削除要素の削除
8noneDOM を変更しないサイドエフェクトのみ

innerHTML:デフォルトの動作

html<div id="content" hx-get="/api/content" hx-swap="innerHTML">
  <!-- ここが置き換わる -->
  初期コンテンツ
</div>

レスポンスの HTML が <div id="content"> の内容として置き換わります。デフォルト動作なので hx-swap="innerHTML" は省略可能です。

outerHTML:要素自体を置き換え

html<div
  id="old-content"
  hx-get="/api/new-content"
  hx-swap="outerHTML"
>
  古いコンテンツ
</div>

レスポンスで返された HTML が、<div id="old-content"> を含めて完全に置き換えます。要素の ID や class も変更したい場合に便利です。

beforeend:リストの末尾に追加

html<ul id="task-list">
  <li>既存のタスク1</li>
  <li>既存のタスク2</li>
</ul>

<button
  hx-post="/api/tasks"
  hx-target="#task-list"
  hx-swap="beforeend"
>
  新しいタスクを追加
</button>

ボタンをクリックすると、新しいタスクが <ul> の最後の子要素として追加されます。既存のタスクはそのまま残ります。

afterbegin:リストの先頭に追加

html<ul id="notification-list">
  <li>通知1</li>
  <li>通知2</li>
</ul>

<button
  hx-get="/api/notifications/latest"
  hx-target="#notification-list"
  hx-swap="afterbegin"
>
  最新の通知を取得
</button>

最新の通知がリストの先頭に挿入されます。新しい順に並べたいリストに最適です。

swap の詳細オプション

hx-swap には追加のオプションを指定できます。

html<div
  hx-get="/api/content"
  hx-swap="innerHTML swap:1s settle:0.5s"
>
  コンテンツ
</div>

このコードでは、swap(HTML の挿入)に 1 秒、settle(CSS トランジションの完了)に 0.5 秒かけます。スムーズなアニメーションを実現できます。

スクロール位置の制御

html<button
  hx-get="/api/more-items"
  hx-target="#item-list"
  hx-swap="beforeend scroll:bottom"
>
  さらに読み込む
</button>

scroll:bottom オプションを使うと、新しいコンテンツ追加後に自動的に一番下までスクロールします。チャットアプリなどで便利です。

hx-target:レスポンスの挿入先を指定

基本的な使い方

デフォルトでは、レスポンスは hx-gethx-post を持つ要素自身に挿入されます。hx-target を使うと別の要素を指定できます。

html<button hx-get="/api/users" hx-target="#user-list">
  ユーザー一覧を読み込む
</button>

<div id="user-list">
  <!-- ここにユーザー一覧が表示される -->
</div>

ボタンをクリックすると、ボタン自体ではなく #user-list の内容が更新されます。

CSS セレクタの活用

hx-target には CSS セレクタを指定できます。

html<form hx-post="/api/comments" hx-target=".comment-section">
  <textarea
    name="comment"
    placeholder="コメントを入力"
  ></textarea>
  <button type="submit">投稿</button>
</form>

<div class="comment-section">
  <!-- 既存のコメント -->
</div>

class セレクタや属性セレクタなど、あらゆる CSS セレクタが使用可能です。

特殊なターゲット指定

htmx には便利な特殊キーワードがあります。

#キーワード説明使用例
1this自分自身(デフォルト)hx-target="this"
2closest <selector>最も近い祖先要素hx-target="closest .card"
3find <selector>子孫要素を検索hx-target="find .result"
4next <selector>次の兄弟要素hx-target="next .section"
5previous <selector>前の兄弟要素hx-target="previous .section"

closest:最も近い祖先要素

html<div class="card">
  <h3>ユーザー情報</h3>
  <button
    hx-get="/api/users/123"
    hx-target="closest .card"
    hx-swap="outerHTML"
  >
    詳細を表示
  </button>
</div>

ボタンをクリックすると、最も近い .card クラスを持つ祖先要素(この場合は親の div)全体が置き換わります。

find:子孫要素を検索

html<div class="user-profile">
  <button
    hx-get="/api/users/123/stats"
    hx-target="find .stats-container"
  >
    統計情報を読み込む
  </button>

  <div class="stats-container">
    <!-- ここに統計情報が表示される -->
  </div>
</div>

find を使うと、ボタンの親要素内の .stats-container を探して、そこに結果を挿入します。

next / previous:兄弟要素への操作

html<div class="section-header">
  <h2>セクション1</h2>
  <button
    hx-get="/api/section-content/1"
    hx-target="next .section-content"
  >
    展開
  </button>
</div>

<div class="section-content">
  <!-- ここにコンテンツが読み込まれる -->
</div>

next .section-content により、ボタンの次の兄弟要素である .section-content に結果が挿入されます。

4 つの属性を組み合わせた実践例

ページネーション付きリスト

html<div id="product-list">
  <!-- 商品一覧 -->
  <div class="product">商品1</div>
  <div class="product">商品2</div>
  <div class="product">商品3</div>
</div>

<button
  hx-get="/api/products?page=2"
  hx-target="#product-list"
  hx-swap="beforeend"
>
  さらに読み込む
</button>

このコードでは、hx-get でデータを取得し、hx-target で挿入先を指定し、hx-swap="beforeend" で既存のリストの末尾に追加します。

サーバー側は次のページの商品だけを HTML で返します。

javascriptapp.get('/api/products', (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const products = getProductsFromDB(page);

  const html = products
    .map((p) => `<div class="product">${p.name}</div>`)
    .join('');

  res.send(html);
});

インラインフォーム編集

html<div id="user-123" class="user-card">
  <h3>山田太郎</h3>
  <p>yamada@example.com</p>

  <button
    hx-get="/api/users/123/edit"
    hx-target="#user-123"
    hx-swap="outerHTML"
  >
    編集
  </button>
</div>

編集ボタンをクリックすると、サーバーから編集フォームが返されます。

javascriptapp.get('/api/users/:id/edit', (req, res) => {
  const user = getUserFromDB(req.params.id);

  res.send(`
    <div id="user-${user.id}" class="user-card">
      <form hx-post="/api/users/${user.id}" hx-swap="outerHTML">
        <input type="text" name="name" value="${user.name}">
        <input type="email" name="email" value="${user.email}">
        <button type="submit">保存</button>
        <button type="button" hx-get="/api/users/${user.id}" hx-target="#user-${user.id}" hx-swap="outerHTML">
          キャンセル
        </button>
      </form>
    </div>
  `);
});

このコードでは、閲覧モードから編集モードへ、そして再び閲覧モードへと、状態を切り替えられます。

保存時の処理は次のようになります。

javascriptapp.post('/api/users/:id', (req, res) => {
  const { name, email } = req.body;
  updateUserInDB(req.params.id, { name, email });

  const user = getUserFromDB(req.params.id);

  res.send(`
    <div id="user-${user.id}" class="user-card">
      <h3>${user.name}</h3>
      <p>${user.email}</p>
      <button hx-get="/api/users/${user.id}/edit" hx-target="#user-${user.id}" hx-swap="outerHTML">
        編集
      </button>
    </div>
  `);
});

リアルタイム検索

html<div class="search-container">
  <input
    type="text"
    name="q"
    placeholder="検索..."
    hx-get="/api/search"
    hx-trigger="keyup changed delay:300ms"
    hx-target="#search-results"
    hx-swap="innerHTML"
  />

  <div id="search-results">
    <!-- 検索結果がここに表示される -->
  </div>
</div>

この実装では、ユーザーが入力を停止してから 300ms 後に検索リクエストが送信されます。入力中は無駄なリクエストが発生しません。

サーバー側では検索結果を HTML で返します。

javascriptapp.get('/api/search', (req, res) => {
  const query = req.query.q;

  if (!query || query.length < 2) {
    res.send('<p>2文字以上入力してください</p>');
    return;
  }

  const results = searchInDB(query);

  if (results.length === 0) {
    res.send('<p>検索結果が見つかりませんでした</p>');
    return;
  }

  const html = results
    .map(
      (r) =>
        `<div class="search-result">
      <h4>${r.title}</h4>
      <p>${r.description}</p>
    </div>`
    )
    .join('');

  res.send(html);
});

削除確認とリスト更新

html<ul id="task-list">
  <li id="task-1">
    タスク1
    <button
      hx-delete="/api/tasks/1"
      hx-confirm="本当に削除しますか?"
      hx-target="closest li"
      hx-swap="outerHTML"
    >
      削除
    </button>
  </li>
  <li id="task-2">
    タスク2
    <button
      hx-delete="/api/tasks/2"
      hx-confirm="本当に削除しますか?"
      hx-target="closest li"
      hx-swap="outerHTML"
    >
      削除
    </button>
  </li>
</ul>

削除ボタンをクリックすると、確認ダイアログが表示され、OK を押すと DELETE リクエストが送信されます。

サーバー側では空の文字列を返すか、何も返さないことで、outerHTML swap により <li> 要素全体が削除されます。

javascriptapp.delete('/api/tasks/:id', (req, res) => {
  deleteTaskFromDB(req.params.id);

  // 空のレスポンスを返すと要素が削除される
  res.send('');
});

エラー処理とローディング状態

ローディングインジケーターの表示

htmx は自動的に htmx-request クラスをリクエスト中の要素に追加します。これを CSS で活用できます。

html<style>
  .htmx-request {
    opacity: 0.5;
    pointer-events: none;
  }

  .htmx-request::after {
    content: ' 読み込み中...';
  }
</style>

<button hx-get="/api/slow-data" hx-target="#result">
  データを取得
</button>

<div id="result"></div>

リクエスト中はボタンが半透明になり、「読み込み中...」のテキストが表示されます。

より詳細なローディング表示

hx-indicator 属性を使うと、任意の要素をローディングインジケーターにできます。

html<style>
  .loading-indicator {
    display: none;
  }

  .htmx-request .loading-indicator {
    display: inline-block;
  }
</style>

<button hx-get="/api/data" hx-indicator="#spinner">
  データを取得
  <span id="spinner" class="loading-indicator">🔄</span>
</button>

リクエスト中だけスピナーが表示されます。

エラーハンドリング

サーバーがエラーステータス(4xx、5xx)を返した場合、htmx は htmx-error クラスを要素に追加します。

html<style>
  .htmx-error {
    border: 2px solid red;
  }
</style>

<form hx-post="/api/users" hx-target="#result">
  <input type="email" name="email" required />
  <button type="submit">登録</button>
</form>

<div id="result"></div>

サーバー側でエラーメッセージを含む HTML を返します。

javascriptapp.post('/api/users', (req, res) => {
  const { email } = req.body;

  if (!isValidEmail(email)) {
    res.status(400).send(`
      <div class="error-message">
        <p>Error 400: 有効なメールアドレスを入力してください</p>
      </div>
    `);
    return;
  }

  try {
    saveUserToDB({ email });
    res.send(`
      <div class="success-message">
        <p>✓ 登録が完了しました</p>
      </div>
    `);
  } catch (error) {
    res.status(500).send(`
      <div class="error-message">
        <p>Error 500: サーバーエラーが発生しました</p>
        <p>${error.message}</p>
      </div>
    `);
  }
});

属性の優先順位と継承

htmx の属性は親要素から子要素へ継承されます。これを活用すると、コードの重複を減らせます。

html<div hx-target="#results" hx-swap="innerHTML">
  <!-- この中のすべてのリクエストは #results に結果を挿入 -->

  <button hx-get="/api/users">ユーザー</button>
  <button hx-get="/api/posts">投稿</button>
  <button hx-get="/api/comments">コメント</button>
</div>

<div id="results">
  <!-- すべての結果がここに表示される -->
</div>

各ボタンに hx-targethx-swap を書く必要がありません。親要素に指定した値が継承されます。

子要素で明示的に指定すると、親の設定を上書きできます。

html<div hx-target="#results" hx-swap="innerHTML">
  <button hx-get="/api/users">ユーザー</button>

  <!-- このボタンだけ異なるターゲット -->
  <button hx-get="/api/special" hx-target="#special-area">
    特別なデータ
  </button>
</div>

<div id="results"></div>
<div id="special-area"></div>

まとめ

htmx の 4 つの主要属性は、シンプルでありながら強力な動的 Web UI の基盤となります。

各属性の役割まとめ

  • hx-get: サーバーからデータを取得する GET リクエストを送信
  • hx-post: サーバーへデータを送信する POST リクエストを送信
  • hx-swap: 取得した HTML をどのように DOM に挿入するかを制御(innerHTML、beforeend など)
  • hx-target: HTML を挿入する先の要素を CSS セレクタで指定

これらを組み合わせることで、JavaScript を書かずに以下のような UI パターンを実装できます。

  • リアルタイム検索
  • インライン編集
  • 無限スクロール
  • 動的フォーム
  • モーダルダイアログ
  • 削除確認

htmx は HTML 属性だけで宣言的に動作を定義できるため、コードの可読性と保守性が高まります。サーバーサイドで HTML を生成する従来の開発スタイルと相性が良く、学習コストも低いのが特徴です。

まずは hx-gethx-target の組み合わせから始めて、徐々に hx-swap のバリエーションや hx-post を使った双方向通信へと発展させていくと、スムーズに習得できるでしょう。

関連リンク