mcpField
H
htmx-expert

htmx-expert

github.com/lullabot開発152 回インストール47 週間前に更新
リポジトリ

htmx-expert について

htmx開発者向けのガイダンススキルです。リクエスト属性、レスポンスの置換、イベント、拡張機能、安全対策を扱い、ハイパーメディアアプリの構築やSPAパターンのhtmx化を支援します。

主な機能

  1. 01htmx属性とリクエストのリファレンス
  2. 02レスポンス置換と状態管理
  3. 03イベントと拡張機能の設定ガイド
  4. 04セキュリティと段階的機能強化
  5. 05デバッグと開発サーバーの説明

活用シーン

  1. 01htmxによるフォーム・検索・ポーリングの実装
  2. 02Ajaxリクエストとレスポンス置換のデバッグ
  3. 03SPAの操作をハイパーメディア方式に変更
  4. 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-getGET リクエストを発行クリック
hx-postPOST リクエストを発行クリック(フォーム:送信)
hx-putPUT リクエストを発行クリック
hx-patchPATCH リクエストを発行クリック
hx-deleteDELETE リクエストを発行クリック
リクエストの制御
  • 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-Reswaphx-swap の値を上書き
HX-Retargethx-target の値を上書き
HX-Triggerクライアント側イベントをトリガー
HX-Trigger-After-Settlesettle 後にトリガー
HX-Trigger-After-Swapswap 後にトリガー
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:beforeRequestAJAX リクエストの前
htmx:afterRequestAJAX リクエストの完了後
htmx:beforeSwapコンテンツの入れ替え前
htmx:afterSwapコンテンツの入れ替え後
htmx:afterSettleDOM の更新完了後
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';
});

セキュリティのベストプラクティス

  1. すべてのユーザーコンテンツをエスケープする:サーバー側のテンプレートエスケープで XSS を防止します
  2. hx-disable を使用する:信頼できないコンテンツの htmx による処理を防止します
  3. リクエスト元を制限する:
    htmx.config.selfRequestsOnly = true;
    
  4. スクリプト処理を無効にする:
    htmx.config.allowScriptTags = false;
    
  5. CSRF トークンを含める:
    <body hx-headers='{"X-CSRF-Token": "{{ csrf_token }}"}'>
    
  6. コンテンツセキュリティポリシー:ブラウザー側の保護を多層化します

設定

主な 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: true
  • HX-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'));

よくある注意点

  1. ID の安定性:CSS トランジションのため、要素の ID は一定に保ちます
  2. 入れ替えのタイミング:既定の入れ替え遅延は 0ms です。トランジションには swap:100ms を使用します
  3. イベントのバブリング:htmx のイベントはバブリングします。データには event.detail を使用します
  4. フォームデータ:リクエストに含まれるのは、名前が設定された入力要素のみです
  5. 履歴:履歴スナップショットには完全な 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 サーバーを作成します:

  1. 静的ファイル(HTML、CSS、JS)を配信する
  2. 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>

追加リソース

詳しい情報については、以下を参照してください:

コメント