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-indicatorhx-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属性も含めて返すと壊れにくいです。

このページの著者

もちもちみかん(システムエンジニア)

社内SEとしてグループ企業向けの業務アプリを要件定義〜運用まで一気通貫で担当しています。

経験:Webアプリ/業務システム

得意:PHP・JavaScript・MySQL・CSS

個人実績:フォーム生成基盤クイズ学習プラットフォーム

詳しいプロフィールはこちら!  もちもちみかんのプロフィール

もちもちみかん0系くん
TOPへ

もちもちみかん.comとは


このサイトでは、コーディングがめんどうくさい人向けのお助けツールとして、フォームやCSSをノーコードで生成できる、
 もちもちみかん.forms
 もちもちみかん.css1
 もちもちみかん.css2
と言ったジェネレーターを用意してます。

また、このサイトを通じて、「もちもちみかん」のかわいさを普及したいとかんがえてます!