htmx 逆引きレシピ
フォーム送信の結果だけ更新するには?
公開日:
最終更新日:
htmxなら、フォーム送信(POST)の結果を指定した結果エリアだけに表示できます。
「保存しました」「入力エラーです」などの結果メッセージや差分UIだけを更新したい管理画面で特に便利です。
ポイント:サーバーはJSONではなく差し替えたいHTML断片を返す設計にすると、実装がシンプルになります。
使用するhtmx属性
hx-post:フォーム送信(POST)でHTML断片を受け取るhx-target:差し替える先(結果エリア)を指定hx-swap:差し替え方(例:innerHTML/outerHTML)hx-indicator:通信中表示(ローディング)を出す要素を指定hx-disabled-elt:通信中の連打防止(送信ボタンを一時的に無効化)- (応用)
hx-swap-oob:ターゲット以外の要素も同時に差し替える
利用シーン
- 「最小構成:結果エリアだけ更新」:送信後の「保存しました」など、結果メッセージだけを差し替えたい
- 「エラー表示も結果エリアにまとめる」:成功/入力エラーを同じ結果エリアに返して、画面側の分岐を減らしたい
- 「応用:結果+フォーム初期化(hx-swap-oob)」:結果表示に加えて、送信成功後にフォームを空に戻す(初期化)も同時にやりたい
① 最小構成:結果エリアだけ更新
フォームに hx-post を付け、送信結果(HTML断片)を hx-target の結果エリアに差し替えます。
まずはこの形をテンプレにすると、他のフォームにも横展開しやすくなります。
サンプル(HTML)
<!-- 送信フォーム(結果だけ更新) -->
<form
id="DEMO_FORM_1"
class="FORM"
method="post"
hx-post="/htmx/demo/_form_1.php"
hx-target="#DEMO_FORM_1_RESULT"
hx-swap="innerHTML"
hx-indicator="#DEMO_FORM_1_LOADING"
hx-disabled-elt="#DEMO_FORM_1_SUBMIT"
>
<label>
お名前
<input type="text" name="name" maxlength="16" autocomplete="name">
</label>
<label>
メモ
<textarea name="memo" rows="3" maxlength="48"></textarea>
</label>
<button id="DEMO_FORM_1_SUBMIT" type="submit" class="BTN">
送信
</button>
<span id="DEMO_FORM_1_LOADING" class="LOADING" aria-live="polite">
送信中…
</span>
</form>
<!-- 結果表示エリア(ここだけ更新される) -->
<div id="DEMO_FORM_1_RESULT" class="FORM-RESULT" aria-live="polite">
ここに結果が表示されます。
</div>
サンプル(PHP)
<?php
declare(strict_types=1);
header('Content-Type: text/html; charset=UTF-8');
// 例:フォーム値(本番ではCSRF対策なども実施)
$name = isset($_POST['name']) ? trim((string)$_POST['name']) : '';
$memo = isset($_POST['memo']) ? trim((string)$_POST['memo']) : '';
$nameEsc = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
$memoEsc = htmlspecialchars($memo, ENT_QUOTES, 'UTF-8');
// 例:成功メッセージをHTMLで返す(結果エリアのinnerHTMLになる)
echo <<<EOS
<div class="FORM-RESULT is-ok">
<strong>送信しました。</strong>
<div>お名前:{$nameEsc}</div>
<div>メモ:{$memoEsc}</div>
</div>
EOS;
デモ
デモ:最小構成(結果エリアだけ更新)
ここに結果が表示されます。
解説
-
フォームに
hx-postを付けると、送信結果をページ遷移なしで受け取れます。 -
hx-targetを「結果エリア」にしておくと、フォーム自体はそのまま、結果だけ差し替えられます。 -
hx-indicatorとhx-disabled-eltを付けると、通信中のローディング表示と連打防止ができます。
② エラー表示も結果エリアにまとめる
入力エラー時も、同じ結果エリアに「エラーHTML」を返すと、画面側の分岐が減ってテンプレ化しやすくなります。
(※デモではわかりやすさ優先で、エラーでも200で返す想定です)
サンプル(HTML)
<form
id="DEMO_FORM_2"
class="FORM"
method="post"
hx-post="/htmx/demo/_form_2.php"
hx-target="#DEMO_FORM_2_RESULT"
hx-swap="innerHTML"
hx-indicator="#DEMO_FORM_2_LOADING"
hx-disabled-elt="#DEMO_FORM_2_SUBMIT"
>
<label>
メール
<input type="email" name="email" maxlength="64" autocomplete="email">
</label>
<label>
内容
<textarea name="body" rows="3" maxlength="128"></textarea>
</label>
<button id="DEMO_FORM_2_SUBMIT" type="submit" class="BTN">
送信
</button>
<span id="DEMO_FORM_2_LOADING" class="LOADING" aria-live="polite">
送信中…
</span>
</form>
<div id="DEMO_FORM_2_RESULT" class="FORM-RESULT" aria-live="polite">
ここに成功/エラーが表示されます。
</div>
サンプル(PHP)
<?php
declare(strict_types=1);
header('Content-Type: text/html; charset=UTF-8');
$email = isset($_POST['email']) ? trim((string)$_POST['email']) : '';
$body = isset($_POST['body']) ? trim((string)$_POST['body']) : '';
$errors = [];
// 例:簡易バリデーション
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = 'メールアドレスを正しく入力してください。';
}
if ($body === '') {
$errors[] = '内容を入力してください。';
}
if ($errors) {
echo '<div class="FORM-RESULT is-ng">';
echo '<strong>入力エラーがあります。</strong>';
echo '<ul>';
foreach ($errors as $e) {
echo '<li>' . htmlspecialchars($e, ENT_QUOTES, 'UTF-8') . '</li>';
}
echo '</ul>';
echo '</div>';
exit;
}
$emailEsc = htmlspecialchars($email, ENT_QUOTES, 'UTF-8');
// 例:本来はここで保存/送信処理
echo <<<EOS
<div class="FORM-RESULT is-ok">
<strong>送信しました。</strong>
<div>メール:{$emailEsc}</div>
</div>
EOS;
デモ
デモ:エラー表示も結果エリアにまとめる
ここに成功/エラーが表示されます。
解説
- 成功/失敗を同じ結果エリアに返すと、画面側の分岐が減り、テンプレ化しやすくなります。
- 結果エリアのHTMLは、成功は is-ok / エラーは is-ng のようにclassで表現すると整頓しやすいです。
- 本番では、バリデーション失敗は 422 を返す、などステータスコード設計も検討するとより堅くなります。
③ 応用:結果+フォーム初期化(hx-swap-oob)
送信成功後にフォーム入力をクリアしたい場合、レスポンス側で hx-swap-oob を使うと、
「結果エリア更新」と「フォームの差し替え(初期化)」を同時に行えます。
サンプル(HTML)
<form
id="DEMO_FORM_3"
class="FORM"
method="post"
hx-post="/htmx/demo/_form_3.php"
hx-target="#DEMO_FORM_3_RESULT"
hx-swap="innerHTML"
hx-indicator="#DEMO_FORM_3_LOADING"
hx-disabled-elt="#DEMO_FORM_3_SUBMIT"
hx-swap-oob="outerHTML:#DEMO_FORM_3"
>
<label>
タイトル
<input type="text" name="title">
</label>
<label>
本文
<textarea name="text" rows="3"></textarea>
</label>
<button id="DEMO_FORM_3_SUBMIT" type="submit" class="BTN">
保存(デモ)
</button>
<span id="DEMO_FORM_3_LOADING" class="LOADING" aria-live="polite">保存中…</span>
</form>
<div id="DEMO_FORM_3_RESULT" class="FORM-RESULT" aria-live="polite">
ここに結果が表示されます。
</div>
サンプル(PHP)
<?php
declare(strict_types=1);
header('Content-Type: text/html; charset=UTF-8');
$title = isset($_POST['title']) ? trim((string)$_POST['title']) : '';
$text = isset($_POST['text']) ? trim((string)$_POST['text']) : '';
$titleEsc = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
// 1) 結果エリア用(hx-target の innerHTML になる)
echo '<div class="FORM-RESULT is-ok">';
echo '<strong>保存しました。</strong>';
echo '<div>タイトル:' . $titleEsc . '</div>';
echo '</div>';
// 2) フォームをOOBで差し替えて初期化(元と同じ属性を返す)
echo <<<EOS
<form
id="DEMO_FORM_3"
class="FORM"
method="post"
hx-post="/htmx/demo/_form_3.php"
hx-target="#DEMO_FORM_3_RESULT"
hx-swap="innerHTML"
hx-indicator="#DEMO_FORM_3_LOADING"
hx-disabled-elt="#DEMO_FORM_3_SUBMIT"
hx-swap-oob="outerHTML:#DEMO_FORM_3"
>
<label>
タイトル
<input type="text" name="title" value="">
</label>
<label>
本文
<textarea name="text" rows="3"></textarea>
</label>
<button id="DEMO_FORM_3_SUBMIT" type="submit" class="BTN">
保存
</button>
<span id="DEMO_FORM_3_LOADING" class="LOADING" aria-live="polite">
保存中…
</span>
</form>
EOS;
コツ:OOBでフォームを差し替えるときは、フォームの中身だけでなくhx属性も返すのが安全です。
デモ
デモ:結果+フォーム初期化(hx-swap-oob)
ここに結果が表示されます。
解説
-
結果エリアは通常通り
hx-targetに入ります(ここではDEMO_FORM_3_RESULT/DEMO_FORM_3_DEMO_RESULT)。 -
同じレスポンス内に
hx-swap-oob="true"を含む要素を返すと、ターゲット以外も差し替えできます。 - フォームをOOBで差し替える場合は、元のフォームと同じidで返し、さらにhx属性も含めて返すと壊れにくいです。
参考リンク
このページの著者
経験:Webアプリ/業務システム
得意:PHP・JavaScript・MySQL・CSS
個人実績:フォーム生成基盤/クイズ学習プラットフォーム 等
詳しいプロフィールはこちら! もちもちみかんのプロフィール