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

htmx を使い始めると、いくつかの重要な属性に何度も触れることになります。特に hx-get
、hx-post
、hx-swap
、hx-target
の 4 つは、htmx の核となる機能を担っているため、これらを理解することが開発のスピードと品質を大きく左右します。
この記事では、htmx の代表的な 4 つの属性に焦点を当て、それぞれの役割や使い方を実例とともに解説していきます。チートシートとして活用できるよう、各属性の基本から応用まで、わかりやすく整理しました。
クイックリファレンス:4 大属性早見表
まず、すぐに使える早見表から確認していきましょう。
hx-get / hx-post:HTTP リクエスト送信
# | 属性 | 用途 | 基本構文 | 主な使用例 |
---|---|---|---|---|
1 | hx-get | データ取得 | hx-get="/api/path" | 一覧表示、検索、詳細取得 |
2 | hx-post | データ送信 | hx-post="/api/path" | フォーム送信、登録、更新 |
3 | hx-put | データ更新 | hx-put="/api/path" | リソース全体の更新 |
4 | hx-patch | 部分更新 | hx-patch="/api/path" | リソースの一部を更新 |
5 | hx-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 挿入モード早見表
# | モード | 動作 | 使用シーン | コード例 |
---|---|---|---|---|
1 | innerHTML | 内容を置換(デフォルト) | コンテンツ更新 | hx-swap="innerHTML" |
2 | outerHTML | 要素ごと置換 | 要素全体の入れ替え | hx-swap="outerHTML" |
3 | beforebegin | 要素の前に挿入 | リスト先頭追加(外側) | hx-swap="beforebegin" |
4 | afterbegin | 最初の子として挿入 | リスト先頭追加(内側) | hx-swap="afterbegin" |
5 | beforeend | 最後の子として挿入 | リスト末尾追加(内側) | hx-swap="beforeend" |
6 | afterend | 要素の後に挿入 | リスト末尾追加(外側) | hx-swap="afterend" |
7 | delete | 要素を削除 | 削除操作 | hx-swap="delete" |
8 | none | DOM 変更なし | サイドエフェクトのみ | 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:挿入先指定早見表
# | 指定方法 | 説明 | 構文例 | 使用場面 |
---|---|---|---|---|
1 | CSS ID | ID 指定 | hx-target="#result" | 固定の結果エリア |
2 | CSS class | クラス指定 | hx-target=".content" | 複数候補から最初の要素 |
3 | this | 自分自身(デフォルト) | hx-target="this" | ボタン自体を更新 |
4 | closest | 最も近い祖先 | hx-target="closest .card" | 親要素を探す |
5 | find | 子孫要素 | hx-target="find .result" | 子要素を探す |
6 | next | 次の兄弟 | hx-target="next div" | すぐ下の要素 |
7 | previous | 前の兄弟 | hx-target="previous div" | すぐ上の要素 |
8 | document.body | body 要素 | 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>
補助的な属性クイックリファレンス
# | 属性 | 役割 | 使用例 |
---|---|---|---|
1 | hx-trigger | イベント指定 | hx-trigger="click" |
2 | hx-confirm | 確認ダイアログ | hx-confirm="本当に削除?" |
3 | hx-indicator | ローディング表示 | hx-indicator="#spinner" |
4 | hx-params | 送信パラメータ制御 | hx-params="name,email" |
5 | hx-vals | 追加データ送信 | hx-vals='{"key":"value"}' |
6 | hx-headers | カスタムヘッダー | hx-headers='{"X-Token":"abc"}' |
7 | hx-include | 追加要素の値も送信 | hx-include="#other-form" |
8 | hx-push-url | URL 履歴に追加 | hx-push-url="true" |
トラブルシューティング早見表
# | 問題 | 原因 | 解決方法 |
---|---|---|---|
1 | レスポンスが表示されない | hx-target の指定ミス | セレクタが正しいか確認 |
2 | 要素が重複する | swap モードが不適切 | innerHTML またはouterHTML に変更 |
3 | リクエストが送信されない | トリガーイベント未発生 | hx-trigger を確認 |
4 | 古いコンテンツが残る | swap モードがbeforeend | innerHTML に変更 |
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 つは特に使用頻度が高く、基本的な機能を支えています。
# | 属性 | 役割 | 使用頻度 |
---|---|---|---|
1 | hx-get | GET リクエストを送信 | ★★★★★ |
2 | hx-post | POST リクエストを送信 | ★★★★★ |
3 | hx-swap | レスポンスの挿入方法を指定 | ★★★★☆ |
4 | hx-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 を使わずに動的なコンテンツ更新を実現するには、以下のような手間が発生します。
XMLHttpRequest
やfetch
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 に挿入するかを指定します。
# | モード | 説明 | 使用シーン |
---|---|---|---|
1 | innerHTML | 要素の内容を置き換え(デフォルト) | コンテンツの更新 |
2 | outerHTML | 要素自体を置き換え | 要素全体の入れ替え |
3 | beforebegin | 要素の直前に挿入 | リストの先頭追加 |
4 | afterbegin | 要素の最初の子として挿入 | リストの先頭追加(内側) |
5 | beforeend | 要素の最後の子として挿入 | リストの末尾追加(内側) |
6 | afterend | 要素の直後に挿入 | リストの末尾追加 |
7 | delete | 要素を削除 | 要素の削除 |
8 | none | DOM を変更しない | サイドエフェクトのみ |
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-get
や hx-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 には便利な特殊キーワードがあります。
# | キーワード | 説明 | 使用例 |
---|---|---|---|
1 | this | 自分自身(デフォルト) | hx-target="this" |
2 | closest <selector> | 最も近い祖先要素 | hx-target="closest .card" |
3 | find <selector> | 子孫要素を検索 | hx-target="find .result" |
4 | next <selector> | 次の兄弟要素 | hx-target="next .section" |
5 | previous <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-target
や hx-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-get
と hx-target
の組み合わせから始めて、徐々に hx-swap
のバリエーションや hx-post
を使った双方向通信へと発展させていくと、スムーズに習得できるでしょう。
関連リンク
- article
htmx 属性チートシート:hx-get/hx-post/hx-swap/hx-target 早見表【実例付き】
- article
htmx × Express/Node.js 高速セットアップ:テンプレ・部分テンプレ構成の定石
- article
htmx パフォーマンス実測:同等 UI を SPA/SSR/htmx で作った場合の応答時間比較
- article
htmx の設計思想を図解で理解する:HTML over the Wire とハイパーメディアの本質
- article
htmx 技術ロードマップ 2025:SPA 脱却とサーバ駆動 UI の現在地
- article
htmx のエラーハンドリングとデバッグのコツ
- article
Preact チートシート【保存版】:JSX/Props/Events/Ref の書き方早見表
- article
Playwright コマンド&テストランナー チートシート【保存版スニペット集】
- article
htmx 属性チートシート:hx-get/hx-post/hx-swap/hx-target 早見表【実例付き】
- article
Homebrew コマンドチートシート 2025:毎日使う 60 コマンド即参照リスト
- article
Node.js クリーンアーキテクチャ実践:アダプタ/ユースケース/エンティティの分離
- article
gpt-oss のモデルルーティング設計:サイズ別・ドメイン別・コスト別の自動切替
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来