htmx-expert について
htmx開発者向けのガイダンススキルです。リクエスト属性、レスポンスの置換、イベント、拡張機能、安全対策を扱い、ハイパーメディアアプリの構築やSPAパターンのhtmx化を支援します。
主な機能
- 01htmx属性とリクエストのリファレンス
- 02レスポンス置換と状態管理
- 03イベントと拡張機能の設定ガイド
- 04セキュリティと段階的機能強化
- 05デバッグと開発サーバーの説明
活用シーン
- 01htmxによるフォーム・検索・ポーリングの実装
- 02Ajaxリクエストとレスポンス置換のデバッグ
- 03SPAの操作をハイパーメディア方式に変更
- 04htmxコンテンツへのセキュリティ対策
MCP 対応
READMEにはMCPとの関係について記載がありません。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ lullabot/ htmx-expert --skill htmx-expertインストール後、Claude Code で/htmx-expertを入力して読み込まれたか確認します。
同梱ファイルreferences/attributes-quick-ref.mdreferences/server-patterns.md
詳細
htmx エキスパート
このスキルでは、JavaScript を使わずに最新のブラウザー機能へ直接アクセスできるよう HTML を拡張するライブラリ、htmx の開発に関する包括的なガイダンスを提供します。
基本理念
htmx は、ハイパーメディアを第一に考える Web 開発へのパラダイムシフトを体現しています。JSON API を備えたプレゼンテーション層として HTML を扱うのではなく、htmx は HTML を拡張し、AJAX リクエスト、CSS トランジション、WebSocket、Server-Sent Events を直接処理できるようにします。サーバーは JSON ではなく、HTML フラグメントを返します。
このスキルを使用する場面
- htmx 属性とインタラクションを実装するとき
- ハイパーメディア駆動型アプリケーションを構築するとき
- htmx のリクエスト/レスポンスサイクルをデバッグするとき
- SPA のパターンを htmx のアプローチに置き換えるとき
- htmx のイベントとライフサイクルを理解するとき
- htmx 拡張機能を設定するとき
- 適切なセキュリティ対策を実装するとき
基本属性のリファレンス
HTTP 動詞属性
| 属性 | 目的 | デフォルトのトリガー |
|---|---|---|
hx-get | GET リクエストを発行 | クリック |
hx-post | POST リクエストを発行 | クリック(フォーム:送信) |
hx-put | PUT リクエストを発行 | クリック |
hx-patch | PATCH リクエストを発行 | クリック |
hx-delete | DELETE リクエストを発行 | クリック |
リクエストの制御
-
hx-trigger:リクエストを発行するタイミングをカスタマイズします
- 修飾子:
changed、delay:Xms、throttle:Xms、once - 特別なトリガー:
load、revealed、every Xs - 拡張指定:
from:<selector>、target:<selector>
- 修飾子:
-
hx-include:リクエストに追加の要素値を含めます
-
hx-params:送信するパラメーターを絞り込みます(
*、none、not <param>、<param>) -
hx-headers:カスタムヘッダーを追加します(JSON 形式)
-
hx-vals:リクエストに値を追加します(JSON 形式)
-
hx-encoding:エンコーディングを設定します(ファイルのアップロードでは
multipart/form-data)
レスポンスの処理
-
hx-target:レスポンスの内容を配置する場所を指定します
- 拡張セレクター:
this、closest <sel>、next <sel>、previous <sel>、find <sel>
- 拡張セレクター:
-
hx-swap:内容の挿入方法を指定します
innerHTML(デフォルト)、outerHTML、beforebegin、afterbegin、beforeend、afterend、delete、none- 修飾子:
swap:Xms、settle:Xms、scroll:top、show:top
-
hx-select:スワップするレスポンスの一部を選択します
-
hx-select-oob:帯域外スワップの対象となる要素を選択します
状態管理
- hx-push-url:ブラウザーの履歴に URL を追加します
- hx-replace-url:履歴内の現在の URL を置き換えます
- hx-history:履歴スナップショットの動作を制御します
- hx-history-elt:スナップショットを取得する要素を指定します
UI インジケーター
- hx-indicator:リクエスト中に表示する要素を指定します(
htmx-indicatorクラスを追加します) - hx-disabled-elt:リクエスト中に無効化する要素を指定します
セキュリティと制御
- hx-confirm:リクエスト前に確認ダイアログを表示します
- hx-validate:フォーム以外の要素でも HTML5 検証を有効にします
- hx-disable:要素とその子孫要素での htmx 処理を無効にします
- hx-sync:要素間のリクエストを調整します
実装パターン
基本的な AJAX パターン
<button hx-get="/api/data"
hx-target="#result"
hx-swap="innerHTML">
Load Data
</button>
<div id="result"></div>
アクティブ検索
<input type="search"
name="q"
hx-get="/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#search-results">
<div id="search-results"></div>
無限スクロール
<div hx-get="/items?page=2"
hx-trigger="revealed"
hx-swap="afterend">
Loading more...
</div>
ポーリング
<div hx-get="/status"
hx-trigger="every 5s"
hx-swap="innerHTML">
Status: Unknown
</div>
フォームの送信
<form hx-post="/submit"
hx-target="#response"
hx-swap="outerHTML">
<input name="email" type="email" required>
<button type="submit">Submit</button>
</form>
帯域外更新
サーバーのレスポンスで複数の要素を更新できます:
<div id="main-content">Updated content</div>
<div id="notification" hx-swap-oob="true">New notification!</div>
<span id="counter" hx-swap-oob="true">42</span>
読み込みインジケーター
<button hx-get="/slow-endpoint"
hx-indicator="#spinner">
Load
</button>
<img id="spinner" class="htmx-indicator" src="/spinner.gif">
インジケーター用の CSS:
.htmx-indicator {
opacity: 0;
transition: opacity 200ms ease-in;
}
.htmx-request .htmx-indicator {
opacity: 1;
}
サーバーレスポンスのパターン
HTML フラグメントを返す
サーバーエンドポイントは JSON ではなく HTML を返します:
# Flask example
@app.route('/search')
def search():
q = request.args.get('q', '')
results = search_database(q)
return render_template('_search_results.html', results=results)
レスポンスヘッダー
htmx は特別なヘッダーを認識します:
| ヘッダー | 目的 |
|---|---|
HX-Location | クライアント側のリダイレクト(コンテキスト付き) |
HX-Push-Url | 履歴に URL を追加 |
HX-Redirect | ページ全体をリダイレクト |
HX-Refresh | ページを再読み込み |
HX-Reswap | hx-swap の値を上書き |
HX-Retarget | hx-target の値を上書き |
HX-Trigger | クライアント側イベントをトリガー |
HX-Trigger-After-Settle | settle 後にトリガー |
HX-Trigger-After-Swap | swap 後にトリガー |
htmx リクエストの検出
HX-Request ヘッダーを確認して、htmx リクエストと通常のリクエストを区別します:
if request.headers.get('HX-Request'):
return render_template('_partial.html')
else:
return render_template('full_page.html')
イベント
主要なイベント
| イベント | 発生するタイミング |
|---|---|
htmx:load | 要素が DOM に読み込まれたとき |
htmx:configRequest | リクエスト送信前(パラメーター/ヘッダーの変更) |
htmx:beforeRequest | AJAX リクエストの前 |
htmx:afterRequest | AJAX リクエストの完了後 |
htmx:beforeSwap | コンテンツの入れ替え前 |
htmx:afterSwap | コンテンツの入れ替え後 |
htmx:afterSettle | DOM の更新完了後 |
htmx:confirm | 確認ダイアログの表示前 |
htmx:validation:validate | カスタム検証フック |
イベント処理
hx-on* を使用する場合:
<button hx-get="/data"
hx-on:htmx:before-request="console.log('Starting...')"
hx-on:htmx:after-swap="console.log('Done!')">
Load
</button>
JavaScript を使用する場合:
document.body.addEventListener('htmx:configRequest', function(evt) {
evt.detail.headers['X-Custom-Header'] = 'value';
});
セキュリティのベストプラクティス
- すべてのユーザーコンテンツをエスケープする:サーバー側のテンプレートエスケープで XSS を防止します
- hx-disable を使用する:信頼できないコンテンツの htmx による処理を防止します
- リクエスト元を制限する:
htmx.config.selfRequestsOnly = true; - スクリプト処理を無効にする:
htmx.config.allowScriptTags = false; - CSRF トークンを含める:
<body hx-headers='{"X-CSRF-Token": "{{ csrf_token }}"}'> - コンテンツセキュリティポリシー:ブラウザー側の保護を多層化します
設定
主な htmx.config オプション:
htmx.config.defaultSwapStyle = 'innerHTML';
htmx.config.timeout = 0; // Request timeout (0 = none)
htmx.config.historyCacheSize = 10;
htmx.config.globalViewTransitions = false;
htmx.config.scrollBehavior = 'instant'; // or 'smooth', 'auto'
htmx.config.selfRequestsOnly = false;
htmx.config.allowScriptTags = true;
htmx.config.allowEval = true;
または、メタタグを使用します:
<meta name="htmx-config" content='{"selfRequestsOnly":true}'>
拡張機能
拡張機能の読み込み
<script src="https://unpkg.com/htmx-ext-<name>@<version>/<name>.js"></script>
<body hx-ext="extension-name">
一般的な拡張機能
- head-support:リクエスト間で head タグの情報をマージします
- idiomorph:モーフィングによる入れ替え(要素の状態を保持します)
- sse:Server-Sent Events をサポートします
- ws:WebSocket をサポートします
- preload:コンテンツを事前読み込みします
- response-targets:HTTP ステータスに基づいてターゲットを指定します
デバッグ
ログを有効にします:
htmx.logAll();
Network タブでリクエストヘッダーを確認します:
HX-Request: trueHX-Target: <target-id>HX-Trigger: <trigger-id>HX-Current-URL: <page-url>
プログレッシブエンハンスメント
段階的な機能低下に対応した構成:
<form action="/search" method="POST">
<input name="q"
hx-get="/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#results">
<button type="submit">Search</button>
</form>
<div id="results"></div>
JavaScript を使用しないユーザーにはフォーム送信が、JavaScript を使用するユーザーには AJAX が提供されます。
サードパーティとの連携
htmx で読み込んだコンテンツにライブラリを初期化します:
htmx.onLoad(function(content) {
content.querySelectorAll('.datepicker').forEach(el => {
new Datepicker(el);
});
});
プログラムから追加した htmx コンテンツの場合:
htmx.process(document.getElementById('new-content'));
よくある注意点
- ID の安定性:CSS トランジションのため、要素の ID は一定に保ちます
- 入れ替えのタイミング:既定の入れ替え遅延は 0ms です。トランジションには
swap:100msを使用します - イベントのバブリング:htmx のイベントはバブリングします。データには
event.detailを使用します - フォームデータ:リクエストに含まれるのは、名前が設定された入力要素のみです
- 履歴:履歴スナップショットには完全な DOM 状態ではなく、innerHTML が保存されます
開発環境の要件
htmx には HTTP が必要です(file:// は不可)
HTML ファイルをファイルシステムから直接開いた場合(file:// URL)、htmx は動作しません。次の理由により、htmx:invalidPath エラーが発生します:
- ブラウザーは
file://URL からのクロスオリジンリクエストをブロックします - htmx はエンドポイントへの HTTP リクエストを必要とします
解決策:htmx アプリケーションは必ず HTTP サーバー経由で配信します:
# Simple Python server (recommended for development)
python3 -m http.server 8000
# Or create a custom server with API endpoints
python3 server.py
最小限の開発サーバーパターン
htmx の例やプロトタイプでは、次の処理を行うシンプルな Python サーバーを作成します:
- 静的ファイル(HTML、CSS、JS)を配信する
- HTML フラグメントを返す API エンドポイントを提供する
from http.server import HTTPServer, SimpleHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
class HtmxHandler(SimpleHTTPRequestHandler):
def do_GET(self):
path = urlparse(self.path).path
if path.startswith("/api/"):
# Return HTML fragment
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.end_headers()
self.wfile.write(b"<div>Response HTML</div>")
else:
# Serve static files
super().do_GET()
HTTPServer(("", 8000), HtmxHandler).serve_forever()
実践的な実装上の注意点
CSS スピナーによる読み込みインジケーター
パフォーマンス向上のため、画像ファイルの代わりに CSS のみでスピナーを作成します:
<button hx-get="/api/slow"
hx-indicator="#spinner">
Load
<span id="spinner" class="spinner htmx-indicator"></span>
</button>
<style>
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: inline-block; }
.spinner {
width: 20px;
height: 20px;
border: 2px solid #f3f3f3;
border-top: 2px solid #3d72d7;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
適切なトリガーを使った入力検索
UX の向上のため、keyup changed ではなく input changed を使用します(貼り付けや自動入力にも対応します):
<input type="search"
name="q"
hx-get="/api/search"
hx-trigger="input changed delay:300ms, search"
hx-target="#results">
search トリガーは、検索入力欄のクリアボタン(X)にも対応します。
ポーリングによる自己ターゲット指定
要素自身を置き換える場合(ポーリング)は、hx-target="this" を使用します:
<div hx-get="/api/time"
hx-trigger="load, every 2s"
hx-target="this"
hx-swap="innerHTML">
Loading...
</div>
closest を使った行の更新
各行に独自の更新ボタンがあるリスト項目の場合:
<li id="item-1">
<span>Item 1</span>
<button hx-get="/api/update-item/1"
hx-target="closest li"
hx-swap="outerHTML">
Update
</button>
</li>
サーバーは新しい htmx 属性を保持した完全な <li> 要素を返します。
イベント属性の構文
hx-on:: 構文では、htmx イベントに二重コロンを使用します:
<button hx-on::before-request="console.log('starting')">
<button hx-on:htmx:before-request="console.log('starting')">
複数のトリガーの組み合わせ
トリガーはコンマで区切ります:
<div hx-get="/api/data"
hx-trigger="load, every 5s, click from:#refresh-btn">
読み込み状態を備えたフォーム POST
UX を完全なものにするには、hx-indicator と hx-disabled-elt を組み合わせます:
<form hx-post="/api/submit"
hx-target="#result"
hx-indicator="#spinner"
hx-disabled-elt="find button">
<input name="email" required>
<button type="submit">
Submit
<span id="spinner" class="spinner htmx-indicator"></span>
</button>
</form>
追加リソース
詳しい情報については、以下を参照してください:
- 公式ドキュメント:https://htmx.org/docs/
- 属性リファレンス:https://htmx.org/reference/
- 例:https://htmx.org/examples/