htmx 逆引きレシピ
失敗時にエラーだけ差し替えるには?(422など)

公開日:
最終更新日:

通常の成功レスポンスと、入力エラー/権限エラー時の表示先を分けたい場面はよくあります。

このページでは、失敗時だけ target / swap を切り替え、エラー領域のみ安全に差し替えるパターンをまとめます。

使用するhtmx属性

タグ:hx-target / hx-swap / hx-on / hx-request

  • hx-target:通常時の反映先を定義し、失敗時はイベントで動的に差し替えます。
  • hx-swap:成功時の標準swapを決め、失敗時だけ別戦略(例:innerHTML)へ切り替えます。
  • hx-onhtmx:beforeRequest / htmx:beforeSwap でレスポンス状況に応じた振り分けを行います。
  • hx-request:タイムアウト等を指定し、フォーム送信の振る舞いを明示します(HTMLレスポンス前提)。

利用シーン

  • 入力ミスはフォーム直下に表示:422(バリデーション)だけをエラー枠へ差し替え、入力欄やフォーム全体は崩さずに伝えたい
  • 権限エラーはバナーで表示:401/403 はページ上部にまとめて通知し、作業中でも気づける導線にしたい
  • 成功時は通常表示:200時は結果領域だけ更新して、画面全体を動かさずスムーズに完了させたい

共通PHP(全文)

3つのデモで使う文言・初期値・共通関数は1ファイルへ集約します。
以降のサンプルからはこの共通ファイルを require_once して再利用します。

PHP(_show_errors_data.php)

/htmx/demo/_show_errors_data.php
<?php
// 型を厳密に扱う
declare(strict_types=1);

// 共有するデモ設定を配列で持つ
$SHOW_ERRORS_DEMO_CONFIG = [
	'1' => [
		'title' => 'DEMO1: 入力チェック(422)',
		'defaultName' => 'もちもち みかん',
		'defaultEmail' => 'mochi@example.com',
		'hint' => 'メールを空欄にすると422を返します。',
	],
	'2' => [
		'title' => 'DEMO2: 権限エラー(401/403)',
		'defaultName' => 'tanaka',
		'defaultEmail' => 'tanaka@example.com',
		'hint' => 'role=guest は401、role=viewer は403、role=editor は成功です。',
	],
	'3' => [
		'title' => 'DEMO3: 成功/失敗でswap戦略を分離',
		'defaultName' => 'suzuki',
		'defaultEmail' => 'suzuki@example.com',
		'hint' => 'メールの形式が不正だと422を返します。',
	],
];

// DEMO2で使うロール候補
$SHOW_ERRORS_ROLES = [
	'guest' => 'guest(未ログイン想定)',
	'viewer' => 'viewer(閲覧のみ)',
	'editor' => 'editor(更新可)',
];

// HTMLエスケープ関数
function show_errors_h(string $s): string {
	// 特殊文字をエスケープして返す
	return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

// 空白を詰めた文字列を返す
function show_errors_clean(string $s): string {
	// 前後空白を除去して返す
	return trim($s);
}

// エラーボックスHTMLを返す
function show_errors_error_box(string $title, array $messages): string {
	// 先頭HTMLを作る
	$html = '<div class="FORM-RESULT is-ng"><strong>' . show_errors_h($title) . '</strong><ul>';

	// メッセージを1件ずつ連結する
	foreach ($messages as $msg) {
		// liを追加する
		$html .= '<li>' . show_errors_h((string)$msg) . '</li>';
	}

	// 閉じタグを連結する
	$html .= '</ul></div>';

	// 完成HTMLを返す
	return $html;
}

// バナーHTMLを返す
function show_errors_banner_box(string $label, string $message): string {
	// バナーを返す
	return '<div class="FORM-RESULT is-ng"><strong>' . show_errors_h($label) . '</strong> ' . show_errors_h($message) . '</div>';
}

// 成功ブロックHTMLを返す
function show_errors_success_box(string $title, array $rows): string {
	// 先頭HTMLを作る
	$html = '<div class="FORM-RESULT is-ok"><strong>' . show_errors_h($title) . '</strong><ul>';

	// 行を1件ずつ連結する
	foreach ($rows as $row) {
		// liを追加する
		$html .= '<li>' . show_errors_h((string)$row) . '</li>';
	}

	// 閉じタグを連結する
	$html .= '</ul></div>';

	// 完成HTMLを返す
	return $html;
}

① 最小構成(422でエラー領域のみ更新)

成功(200)は結果領域へ反映し、入力エラー(422)だけフォーム直下のエラー領域へ振り分けます。
htmx:beforeSwap で失敗時の targetswap を切り替える最小構成です。
※HTTPステータス(422/401/403)は、ブラウザの開発者ツール(Network)で確認できます。

HTML

_demo_htmx_1.php
<?php
// 型を厳密に扱う
declare(strict_types=1);

// 共通データを読み込む
require_once("{$_SERVER[ 'DOCUMENT_ROOT' ]}/htmx/demo/_show_errors_data.php");

// DEMO1設定を取り出す
$cfg = $SHOW_ERRORS_DEMO_CONFIG['1'];
?>
<div class="DEMO">

	<h4><?= show_errors_h((string)$cfg['title']) ?></h4>
	<p class="HTMX-NOTE"><?= show_errors_h((string)$cfg['hint']) ?></p>

	<form
		id="DEMO1_FORM"
		class="FORM"
		method="post"
		hx-post="/htmx/demo/_show_errors_submit.php"
		hx-target="#DEMO1_RESULT"
		hx-swap="innerHTML"
		hx-request='{"timeout":4000}'
		hx-on::before-request="
			document.getElementById('DEMO1_ERRORS').innerHTML = '';
		"
		>

		<input type="hidden" name="demo" value="1">

		<label>
			名前
			<input type="text" name="name" maxlength="64" value="<?= show_errors_h((string)$cfg['defaultName']) ?>">
		</label>

		<label>
			メール
			<input type="email" name="email" maxlength="128" value="<?= show_errors_h((string)$cfg['defaultEmail']) ?>">
		</label>

		<button type="submit" class="BTN is-ok">送信</button>
	</form>

	<div id="DEMO1_ERRORS" class="MT1rm"></div>

	<div
		id="DEMO1_RESULT"
		class="FORM-RESULT"
		hx-on::before-swap="
			if (event.detail.xhr.status === 422) {
				document.getElementById('DEMO1_RESULT').innerHTML = '<span class=&quot;HTMX-NOTE&quot;>成功時はここが更新されます。</span>';
				event.detail.shouldSwap = true;
				event.detail.isError = false;
				event.detail.target = document.getElementById('DEMO1_ERRORS');
				event.detail.swapOverride = 'innerHTML';
			}
		"
	>
		<span class="HTMX-NOTE">成功時はここが更新されます。</span>
	</div>

</div>

PHP(送信先)

/htmx/demo/_show_errors_submit.php
<?php
// 型を厳密に扱う
declare(strict_types=1);

// 共通データを読み込む
require_once(__DIR__ . '/_show_errors_data.php');

// HTMLを返す
header('Content-Type: text/html; charset=UTF-8');

// リクエストメソッドを取得する
$method = (string)($_SERVER['REQUEST_METHOD'] ?? '');

// POST以外は拒否する
if ($method !== 'POST') {
	// ステータスを405で返す
	http_response_code(405);

	// メッセージを返す
	echo show_errors_banner_box('405 Method Not Allowed', 'POSTで送信してください。');

	// ここで処理を終える
	exit;
}

// demoを受け取る
$demo = show_errors_clean((string)($_POST['demo'] ?? ''));

// nameを受け取る
$name = show_errors_clean((string)($_POST['name'] ?? ''));

// emailを受け取る
$email = show_errors_clean((string)($_POST['email'] ?? ''));

// roleを受け取る
$role = show_errors_clean((string)($_POST['role'] ?? 'guest'));

// nameが空なら補う
if ($name === '') {
	// 補助値を入れる
	$name = '(未入力)';
}

// DEMO1: 入力チェック(422)
if ($demo === '1') {
	// メールが空なら422でエラーを返す
	if ($email === '') {
		// ステータス422を返す
		http_response_code(422);

		// エラーHTMLを返す
		echo show_errors_error_box('入力エラー', ['メールアドレスを入力してください。']);

		// ここで処理を終える
		exit;
	}

	// 正常時は200のまま成功結果を返す
	echo show_errors_success_box('送信に成功しました。', ['名前:' . $name, 'メール:' . $email]);

	// ここで処理を終える
	exit;
}

// DEMO2: 権限チェック(401/403)
if ($demo === '2') {
	// roleがguestなら401を返す
	if ($role === 'guest') {
		// ステータス401を返す
		http_response_code(401);

		// バナー用HTMLを返す
		echo show_errors_banner_box('401 Unauthorized', 'ログインが必要です。');

		// ここで処理を終える
		exit;
	}

	// roleがviewerなら403を返す
	if ($role === 'viewer') {
		// ステータス403を返す
		http_response_code(403);

		// バナー用HTMLを返す
		echo show_errors_banner_box('403 Forbidden', 'この操作を実行する権限がありません。');

		// ここで処理を終える
		exit;
	}

	// 成功結果を返す
	echo show_errors_success_box(
		'更新に成功しました。',
		[
			'ユーザー:' . $name,
			'メール:' . $email,
			'ロール:' . $role,
		]
	);

	// ここで処理を終える
	exit;
}

// DEMO3: 成功はouterHTML差し替え、失敗はエラー領域だけ
if ($demo === '3') {
	// メール形式をチェックする
	$isValidEmail = (bool)filter_var($email, FILTER_VALIDATE_EMAIL);

	// 形式不正なら422でエラーを返す
	if (!$isValidEmail) {
		// ステータス422を返す
		http_response_code(422);

		// エラーHTMLを返す
		echo show_errors_error_box('入力エラー', ['メール形式が不正です。example@example.com の形式で入力してください。']);

		// ここで処理を終える
		exit;
	}

	// 現在時刻を作る
	$now = date('Y-m-d H:i:s');

	// 成功時はouterHTML用にID付きカード全体を返す
	echo '<div id="DEMO3_RESULT_CARD" class="CARD">';

	// 見出しを返す
	echo '<div><strong>最終保存:</strong>' . show_errors_h($now) . '</div>';

	// 本文を返す
	echo '<div><strong>担当者:</strong>' . show_errors_h($name) . '</div>';

	// 本文を返す
	echo '<div><strong>通知先:</strong>' . show_errors_h($email) . '</div>';

	// 補足を返す
	echo '<div class="HTMX-NOTE">成功時は outerHTML でカード全体を更新しました。</div>';

	// 閉じる
	echo '</div>';

	// ここで処理を終える
	exit;
}

// 想定外demoは400を返す
http_response_code(400);

// エラー表示を返す
echo show_errors_banner_box('400 Bad Request', 'demo指定が不正です。');

デモ

DEMO1: 入力チェック(422)

メールを空欄にすると422を返します。

成功時はここが更新されます。

解説

  • 通常は #DEMO1_RESULT へ差し込み、成功表示を更新します。
  • 422 のときだけ event.detail.target#DEMO1_ERRORS に変更し、shouldSwap=true で表示します。
  • 失敗時の更新先を分離するため、フォーム本体や既存結果を崩さず再入力できます。

② 権限エラーをバナーに表示(401/403)

認可失敗(401/403)だけページ上部のバナーへ表示し、結果パネルは成功時だけ更新します。
ログイン切れやロール不足の通知を、フォームと分離して見せたいときの定番です。
※HTTPステータス(422/401/403)は、ブラウザの開発者ツール(Network)で確認できます。

HTML

/_demo_htmx_2.php
<?php
// 型を厳密に扱う
declare(strict_types=1);

// 共通データを読み込む
require_once("{$_SERVER[ 'DOCUMENT_ROOT' ]}/htmx/demo/_show_errors_data.php");

// DEMO2設定を取り出す
$cfg = $SHOW_ERRORS_DEMO_CONFIG['2'];
?>
<div class="DEMO">

	<h4><?= show_errors_h((string)$cfg['title']) ?></h4>
	<p class="HTMX-NOTE"><?= show_errors_h((string)$cfg['hint']) ?></p>

	<form
		id="DEMO2_FORM"
		class="FORM"
		method="post"
		hx-post="/htmx/demo/_show_errors_submit.php"
		hx-target="#DEMO2_RESULT"
		hx-swap="innerHTML"
		hx-request='{"timeout":5000}'
		hx-on::before-request="
			this.setAttribute('hx-target', '#DEMO2_RESULT');
			this.setAttribute('hx-swap', 'innerHTML');
			document.getElementById('DEMO2_BANNER').innerHTML = '<span class=&quot;HTMX-NOTE&quot;>権限エラー時のみ、ここにバナー表示します。</span>';
		"
	>
		<input type="hidden" name="demo" value="2">

		<label>
			ユーザー名
			<input type="text" name="name" maxlength="64" value="<?= show_errors_h((string)$cfg['defaultName']) ?>">
		</label>

		<label>
			メール
			<input type="email" name="email" maxlength="128" value="<?= show_errors_h((string)$cfg['defaultEmail']) ?>">
		</label>

		<label>
			ロール
			<select name="role">
				<?php foreach ($SHOW_ERRORS_ROLES as $value => $label): ?>
					<option value="<?= show_errors_h((string)$value) ?>"><?= show_errors_h((string)$label) ?></option>
				<?php endforeach; ?>
			</select>
		</label>

		<button type="submit" class="BTN is-ok">権限チェック付き送信</button>
	</form>

	<div
		id="DEMO2_RESULT"
		class="FORM-RESULT"
		hx-on::before-swap="
			const s = event.detail.xhr.status;

			if (s === 401 || s === 403) {
				/* 成功表示が残らないように(任意だがおすすめ) */
				document.getElementById('DEMO2_RESULT').innerHTML = '<span class=&quot;HTMX-NOTE&quot;>成功時はここが更新されます。</span>';
				event.detail.shouldSwap = true;
				event.detail.isError = false;
				event.detail.target = document.getElementById('DEMO2_BANNER');
				event.detail.swapOverride = 'innerHTML';
			}
		"
	>
		<span class="HTMX-NOTE">成功時はここが更新されます。</span>
	</div>

	<div id="DEMO2_BANNER" class="FORM-RESULT MT1rm">
		<span class="HTMX-NOTE">権限エラー時のみ、ここにバナー表示します。</span>
	</div>

</div>

PHP(送信先)

/htmx/demo/_show_errors_submit.php
<?php
// 型を厳密に扱う
declare(strict_types=1);

// 共通データを読み込む
require_once(__DIR__ . '/_show_errors_data.php');

// HTMLを返す
header('Content-Type: text/html; charset=UTF-8');

// リクエストメソッドを取得する
$method = (string)($_SERVER['REQUEST_METHOD'] ?? '');

// POST以外は拒否する
if ($method !== 'POST') {
	// ステータスを405で返す
	http_response_code(405);

	// メッセージを返す
	echo show_errors_banner_box('405 Method Not Allowed', 'POSTで送信してください。');

	// ここで処理を終える
	exit;
}

// demoを受け取る
$demo = show_errors_clean((string)($_POST['demo'] ?? ''));

// nameを受け取る
$name = show_errors_clean((string)($_POST['name'] ?? ''));

// emailを受け取る
$email = show_errors_clean((string)($_POST['email'] ?? ''));

// roleを受け取る
$role = show_errors_clean((string)($_POST['role'] ?? 'guest'));

// nameが空なら補う
if ($name === '') {
	// 補助値を入れる
	$name = '(未入力)';
}

// DEMO1: 入力チェック(422)
if ($demo === '1') {
	// メールが空なら422でエラーを返す
	if ($email === '') {
		// ステータス422を返す
		http_response_code(422);

		// エラーHTMLを返す
		echo show_errors_error_box('入力エラー', ['メールアドレスを入力してください。']);

		// ここで処理を終える
		exit;
	}

	// 正常時は200のまま成功結果を返す
	echo show_errors_success_box('送信に成功しました。', ['名前:' . $name, 'メール:' . $email]);

	// ここで処理を終える
	exit;
}

// DEMO2: 権限チェック(401/403)
if ($demo === '2') {
	// roleがguestなら401を返す
	if ($role === 'guest') {
		// ステータス401を返す
		http_response_code(401);

		// バナー用HTMLを返す
		echo show_errors_banner_box('401 Unauthorized', 'ログインが必要です。');

		// ここで処理を終える
		exit;
	}

	// roleがviewerなら403を返す
	if ($role === 'viewer') {
		// ステータス403を返す
		http_response_code(403);

		// バナー用HTMLを返す
		echo show_errors_banner_box('403 Forbidden', 'この操作を実行する権限がありません。');

		// ここで処理を終える
		exit;
	}

	// 成功結果を返す
	echo show_errors_success_box(
		'更新に成功しました。',
		[
			'ユーザー:' . $name,
			'メール:' . $email,
			'ロール:' . $role,
		]
	);

	// ここで処理を終える
	exit;
}

// DEMO3: 成功はouterHTML差し替え、失敗はエラー領域だけ
if ($demo === '3') {
	// メール形式をチェックする
	$isValidEmail = (bool)filter_var($email, FILTER_VALIDATE_EMAIL);

	// 形式不正なら422でエラーを返す
	if (!$isValidEmail) {
		// ステータス422を返す
		http_response_code(422);

		// エラーHTMLを返す
		echo show_errors_error_box('入力エラー', ['メール形式が不正です。example@example.com の形式で入力してください。']);

		// ここで処理を終える
		exit;
	}

	// 現在時刻を作る
	$now = date('Y-m-d H:i:s');

	// 成功時はouterHTML用にID付きカード全体を返す
	echo '<div id="DEMO3_RESULT_CARD" class="CARD">';

	// 見出しを返す
	echo '<div><strong>最終保存:</strong>' . show_errors_h($now) . '</div>';

	// 本文を返す
	echo '<div><strong>担当者:</strong>' . show_errors_h($name) . '</div>';

	// 本文を返す
	echo '<div><strong>通知先:</strong>' . show_errors_h($email) . '</div>';

	// 補足を返す
	echo '<div class="HTMX-NOTE">成功時は outerHTML でカード全体を更新しました。</div>';

	// 閉じる
	echo '</div>';

	// ここで処理を終える
	exit;
}

// 想定外demoは400を返す
http_response_code(400);

// エラー表示を返す
echo show_errors_banner_box('400 Bad Request', 'demo指定が不正です。');

デモ

DEMO2: 権限エラー(401/403)

role=guest は401、role=viewer は403、role=editor は成功です。

成功時はここが更新されます。
権限エラー時のみ、ここにバナー表示します。

解説

  • 成功時は #DEMO2_RESULT を通常更新します。
  • 401/403 のときだけ #DEMO2_BANNER を target に切り替え、バナー表示へ統一します。
  • 権限系エラーを共通バナーへ集約すると、画面全体で通知体験を揃えやすくなります。

③ 成功/失敗でswap戦略を使い分け

成功時はカード全体を outerHTML で置換し、失敗時はエラー枠だけ innerHTML 更新します。
「成功時は大きく更新、失敗時は局所更新」の使い分けを、同じフォームで実現する例です。
※HTTPステータス(422/401/403)は、ブラウザの開発者ツール(Network)で確認できます。

HTML

/_demo_htmx_3.php
<?php
// 型を厳密に扱う
declare(strict_types=1);

// 共通データを読み込む
require_once("{$_SERVER[ 'DOCUMENT_ROOT' ]}/htmx/demo/_show_errors_data.php");

// DEMO3設定を取り出す
$cfg = $SHOW_ERRORS_DEMO_CONFIG['3'];
?>
<div
	class="DEMO"
	hx-on::before-swap="
		if (event.detail.xhr && event.detail.xhr.status === 422) {

			const req = event.detail.requestConfig && event.detail.requestConfig.elt;
			if (req && req.id === 'DEMO3_FORM') {

				// (任意)成功表示が残るのを防ぐ
				const card = document.getElementById('DEMO3_RESULT_CARD');
				if (card) {
					card.innerHTML =
						'<div><strong>最終保存:</strong>未実行</div>' +
						'<div><strong>備考:</strong>成功時はこのカード全体を outerHTML で差し替えます。</div>';
				}

				// 422でもswapして、エラー枠へ
				event.detail.shouldSwap = true;
				event.detail.isError = false;
				event.detail.target = document.getElementById('DEMO3_ERRORS');
				event.detail.swapOverride = 'innerHTML';
			}
		}
	"
>


	<h4><?= show_errors_h((string)$cfg['title']) ?></h4>
	<p class="HTMX-NOTE"><?= show_errors_h((string)$cfg['hint']) ?></p>

	<form
		id="DEMO3_FORM"
		class="FORM"
		method="post"
		hx-post="/htmx/demo/_show_errors_submit.php"
		hx-target="#DEMO3_RESULT_CARD"
		hx-swap="outerHTML"
		hx-request='{"timeout":5000}'
		hx-on::before-request="
			this.setAttribute('hx-target', '#DEMO3_RESULT_CARD');
			this.setAttribute('hx-swap', 'outerHTML');
			document.getElementById('DEMO3_ERRORS').innerHTML = '';
		"
	>
		<input type="hidden" name="demo" value="3">

		<label>
			担当者
			<input type="text" name="name" maxlength="64" value="<?= show_errors_h((string)$cfg['defaultName']) ?>">
		</label>

		<label>
			通知先メール
			<input type="text" name="email" maxlength="128" value="<?= show_errors_h((string)$cfg['defaultEmail']) ?>">
		</label>

		<button type="submit" class="BTN is-ok">保存(swap切替)</button>
	</form>

	<div id="DEMO3_ERRORS" class="MT1rm"></div>

	<div id="DEMO3_RESULT_CARD" class="CARD">
		<div><strong>最終保存:</strong>未実行</div>
		<div><strong>備考:</strong>成功時はこのカード全体を outerHTML で差し替えます。</div>
	</div>

</div>

PHP(送信先)

/htmx/demo/_show_errors_submit.php
<?php
// 型を厳密に扱う
declare(strict_types=1);

// 共通データを読み込む
require_once(__DIR__ . '/_show_errors_data.php');

// HTMLを返す
header('Content-Type: text/html; charset=UTF-8');

// リクエストメソッドを取得する
$method = (string)($_SERVER['REQUEST_METHOD'] ?? '');

// POST以外は拒否する
if ($method !== 'POST') {
	// ステータスを405で返す
	http_response_code(405);

	// メッセージを返す
	echo show_errors_banner_box('405 Method Not Allowed', 'POSTで送信してください。');

	// ここで処理を終える
	exit;
}

// demoを受け取る
$demo = show_errors_clean((string)($_POST['demo'] ?? ''));

// nameを受け取る
$name = show_errors_clean((string)($_POST['name'] ?? ''));

// emailを受け取る
$email = show_errors_clean((string)($_POST['email'] ?? ''));

// roleを受け取る
$role = show_errors_clean((string)($_POST['role'] ?? 'guest'));

// nameが空なら補う
if ($name === '') {
	// 補助値を入れる
	$name = '(未入力)';
}

// DEMO1: 入力チェック(422)
if ($demo === '1') {
	// メールが空なら422でエラーを返す
	if ($email === '') {
		// ステータス422を返す
		http_response_code(422);

		// エラーHTMLを返す
		echo show_errors_error_box('入力エラー', ['メールアドレスを入力してください。']);

		// ここで処理を終える
		exit;
	}

	// 正常時は200のまま成功結果を返す
	echo show_errors_success_box('送信に成功しました。', ['名前:' . $name, 'メール:' . $email]);

	// ここで処理を終える
	exit;
}

// DEMO2: 権限チェック(401/403)
if ($demo === '2') {
	// roleがguestなら401を返す
	if ($role === 'guest') {
		// ステータス401を返す
		http_response_code(401);

		// バナー用HTMLを返す
		echo show_errors_banner_box('401 Unauthorized', 'ログインが必要です。');

		// ここで処理を終える
		exit;
	}

	// roleがviewerなら403を返す
	if ($role === 'viewer') {
		// ステータス403を返す
		http_response_code(403);

		// バナー用HTMLを返す
		echo show_errors_banner_box('403 Forbidden', 'この操作を実行する権限がありません。');

		// ここで処理を終える
		exit;
	}

	// 成功結果を返す
	echo show_errors_success_box(
		'更新に成功しました。',
		[
			'ユーザー:' . $name,
			'メール:' . $email,
			'ロール:' . $role,
		]
	);

	// ここで処理を終える
	exit;
}

// DEMO3: 成功はouterHTML差し替え、失敗はエラー領域だけ
if ($demo === '3') {
	// メール形式をチェックする
	$isValidEmail = (bool)filter_var($email, FILTER_VALIDATE_EMAIL);

	// 形式不正なら422でエラーを返す
	if (!$isValidEmail) {
		// ステータス422を返す
		http_response_code(422);

		// エラーHTMLを返す
		echo show_errors_error_box('入力エラー', ['メール形式が不正です。example@example.com の形式で入力してください。']);

		// ここで処理を終える
		exit;
	}

	// 現在時刻を作る
	$now = date('Y-m-d H:i:s');

	// 成功時はouterHTML用にID付きカード全体を返す
	echo '<div id="DEMO3_RESULT_CARD" class="CARD">';

	// 見出しを返す
	echo '<div><strong>最終保存:</strong>' . show_errors_h($now) . '</div>';

	// 本文を返す
	echo '<div><strong>担当者:</strong>' . show_errors_h($name) . '</div>';

	// 本文を返す
	echo '<div><strong>通知先:</strong>' . show_errors_h($email) . '</div>';

	// 補足を返す
	echo '<div class="HTMX-NOTE">成功時は outerHTML でカード全体を更新しました。</div>';

	// 閉じる
	echo '</div>';

	// ここで処理を終える
	exit;
}

// 想定外demoは400を返す
http_response_code(400);

// エラー表示を返す
echo show_errors_banner_box('400 Bad Request', 'demo指定が不正です。');

デモ

DEMO3: 成功/失敗でswap戦略を分離

メールの形式が不正だと422を返します。

最終保存:未実行
備考:成功時はこのカード全体を outerHTML で差し替えます。

解説

  • フォームの標準設定は hx-target="#DEMO3_RESULT_CARD" + hx-swap="outerHTML" です。
  • 422 失敗時は #DEMO3_ERRORS へ target を切り替え、innerHTML でメッセージだけ更新します。
  • htmx:beforeRequest で毎回デフォルトを戻すため、次回送信時の挙動がブレません。

次に読むオススメレシピ

このページの著者

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

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

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

得意:PHP・JavaScript・MySQL・CSS

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

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

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

もちもちみかん.comとは


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

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