htmx 逆引きレシピ
依存セレクトを作るには?
公開日:
最終更新日:
依存セレクトは、「上の<select>の選択」に応じて「下の<select>の候補」を切り替えるUIです。
htmxを使えばJavaScriptなしでも、必要な部分だけをサーバーから取得して差し替えるだけで、実務で使える依存セレクトをシンプルに実装できます。
このページでは「部署→メンバー」「カテゴリ→サブカテゴリ「都道府県→市区町村」について、hx-get / hx-trigger / hx-target / hx-swap の基本だけで作る手順をデモ付きで解説します。
使用するhtmx属性
hx-get:親<select>の変更に合わせて、GETで子<select>のHTMLを取得するhx-trigger:リクエストを発火するタイミングを指定(例:change)hx-target:差し替える先の要素(CSSセレクタ)を指定(例:子<select>の#DEMO_CITYなど)hx-swap:差し替え方を指定(このレシピではouterHTMLで子<select>自体を丸ごと入れ替える)
利用シーン
- 「都道府県→市区町村」:上位の選択に応じて、下位の候補だけを切り替えたい
- 「部署→メンバー」:部署を選んだら、その部署のメンバーだけを選べるようにしたい
- 「カテゴリ→サブカテゴリ」:カテゴリ変更に合わせて、サブカテゴリの選択肢を動的に更新したい
① 依存セレクト(部署 → メンバー)
部署を選ぶと、その部署に所属するメンバーだけが候補に出る「依存セレクト」の例です。
JavaScriptなしで、下位の<select>だけを差し替えるので、フォームをシンプルに保てます。
HTML
<div class="DEMO">
<h4>① 依存セレクト(部署 → メンバー)</h4>
<form id="DEMO_DEP_DEPT_FORM" class="FORM">
<label>
部署
<select
id="DEMO_DEPT"
name="dept"
hx-get="/htmx/demo/_dependent_select_1.php"
hx-trigger="change"
hx-target="#DEMO_MEMBER"
hx-swap="outerHTML"
>
<option value="">選択してください</option>
<option value="dev">開発</option>
<option value="sales">営業</option>
<option value="hr">人事</option>
</select>
</label>
<label>
メンバー
<select id="DEMO_MEMBER" name="member" disabled>
<option value="">部署を選択してください</option>
</select>
</label>
</form>
</div>
PHP
<?php
// 型を厳密に扱う(意図しない型変換を防ぐ)
declare(strict_types=1);
// 返すのはHTML(UTF-8)
header('Content-Type: text/html; charset=UTF-8');
// HTMLエスケープ用(XSS対策)
function h(string $s): string {
// 特殊文字を安全な文字列に変換する
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}
// 部署→メンバー(デモ用データ。実務ではDB/APIに置き換える)
$membersMap = [
// 開発
'dev' => [
'tanaka' => '田中',
'sato' => '佐藤',
'suzuki' => '鈴木',
],
// 営業
'sales' => [
'kato' => '加藤',
'kobayashi' => '小林',
'yamada' => '山田',
],
// 人事
'hr' => [
'ito' => '伊藤',
'nakamura' => '中村',
'kimura' => '木村',
],
];
// GETパラメータ dept(部署)を受け取る(なければ空)
$dept = (string)($_GET['dept'] ?? '');
// 前後空白を除去する
$dept = trim($dept);
// 返す<select>の開始タグ(有効状態:disabledなし)
$selectOpen = '<select id="DEMO_MEMBER" name="member">';
// 部署が未選択、または未知の値なら「未選択状態」を返す
if ($dept === '' || !array_key_exists($dept, $membersMap)) {
// disabled付きの<select>を出力する(初期状態)
echo '<select id="DEMO_MEMBER" name="member" disabled>';
// 案内オプションを出力する
echo '<option value="">部署を選択してください</option>';
// selectを閉じる
echo '</select>';
// ここで処理を終了する
exit;
}
// 該当部署のメンバーリスト(配列)を取り出す
$members = $membersMap[$dept];
// 有効な<select>(disabledなし)を出力開始
echo $selectOpen;
// 先頭の案内オプションを出力する
echo '<option value="">メンバーを選択してください</option>';
// メンバーの配列を1件ずつ<option>にする
foreach ($members as $value => $label) {
// value(送信値)を安全にエスケープする
$v = h((string)$value);
// label(表示名)を安全にエスケープする
$l = h((string)$label);
// optionタグを出力する
echo "<option value=\"{$v}\">{$l}</option>";
}
// selectを閉じる
echo '</select>';
デモ
① 依存セレクト(部署 → メンバー)
解説
- 部署<select>に
hx-getを付け、選択が変わるたびにサーバーへGETリクエストを送ります。 hx-trigger="change"で、部署の変更タイミングでだけ発火します。hx-target="#DEMO_MEMBER"で、差し替え対象を「メンバー<select>」に限定します。hx-swap="outerHTML"で、メンバー<select>自体を丸ごと入れ替えます(disabled解除も同時にできる)。- サーバー(PHP)はGETパラメータ
deptを受け取り、該当部署のメンバー一覧を<option>として組み立てます。 - 部署が未選択/不正な値なら、
disabled付きのメンバー<select>を返して「選んでください」状態に戻します。
② 依存セレクト(カテゴリ → サブカテゴリ)
カテゴリを選ぶと、そのカテゴリに対応するサブカテゴリだけが候補に出る「依存セレクト」の例です。
下位の<select>だけを更新するので、画面全体を再読み込みせずに軽快に切り替えられます。
HTML
<div class="DEMO">
<h4>② 依存セレクト(カテゴリ → サブカテゴリ)</h4>
<form id="DEMO_DEP_CATEGORY_FORM" class="FORM">
<label>
カテゴリ
<select
id="DEMO_CATEGORY"
name="category"
hx-get="/htmx/demo/_dependent_select_2.php"
hx-trigger="change"
hx-target="#DEMO_SUBCATEGORY"
hx-swap="outerHTML"
>
<option value="">選択してください</option>
<option value="it">IT</option>
<option value="office">オフィス用品</option>
<option value="food">食品</option>
</select>
</label>
<label>
サブカテゴリ
<select id="DEMO_SUBCATEGORY" name="subcategory" disabled>
<option value="">カテゴリを選択してください</option>
</select>
</label>
</form>
</div>
PHP
<?php
// 型を厳密に扱う(意図しない型変換を防ぐ)
declare(strict_types=1);
// 返すのはHTML(UTF-8)
header('Content-Type: text/html; charset=UTF-8');
// HTMLエスケープ用(XSS対策)
function h(string $s): string {
// 特殊文字を安全な文字列に変換する
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}
// カテゴリ→サブカテゴリ(デモ用データ。実務ではDB/APIに置き換える)
$subMap = [
// IT
'it' => [
'laptop' => 'ノートPC',
'monitor' => 'モニター',
'network' => 'ネットワーク機器',
],
// オフィス用品
'office' => [
'stationery' => '文房具',
'chair' => '椅子・デスク',
'storage' => '収納',
],
// 食品
'food' => [
'drink' => '飲料',
'snack' => 'お菓子',
'fruit' => '果物',
],
];
// GETパラメータ category(カテゴリ)を受け取る(なければ空)
$category = (string)($_GET['category'] ?? '');
// 前後空白を除去する
$category = trim($category);
// 返す<select>の開始タグ(有効状態:disabledなし)
$selectOpen = '<select id="DEMO_SUBCATEGORY" name="subcategory">';
// カテゴリが未選択、または未知の値なら「未選択状態」を返す
if ($category === '' || !array_key_exists($category, $subMap)) {
// disabled付きの<select>を出力する(初期状態)
echo '<select id="DEMO_SUBCATEGORY" name="subcategory" disabled>';
// 案内オプションを出力する
echo '<option value="">カテゴリを選択してください</option>';
// selectを閉じる
echo '</select>';
// ここで処理を終了する
exit;
}
// 該当カテゴリのサブカテゴリリスト(配列)を取り出す
$subs = $subMap[$category];
// 有効な<select>(disabledなし)を出力開始
echo $selectOpen;
// 先頭の案内オプションを出力する
echo '<option value="">サブカテゴリを選択してください</option>';
// サブカテゴリの配列を1件ずつ<option>にする
foreach ($subs as $value => $label) {
// value(送信値)を安全にエスケープする
$v = h((string)$value);
// label(表示名)を安全にエスケープする
$l = h((string)$label);
// optionタグを出力する
echo "<option value=\"{$v}\">{$l}</option>";
}
// selectを閉じる
echo '</select>';
デモ
② 依存セレクト(カテゴリ → サブカテゴリ)
解説
- カテゴリ<select>に
hx-getを付け、選択が変わるたびにサーバーへGETリクエストを送ります。 hx-trigger="change"で、カテゴリの変更タイミングでだけ発火します。hx-target="#DEMO_SUBCATEGORY"で、差し替え対象を「サブカテゴリ<select>」に限定します。hx-swap="outerHTML"で、サブカテゴリ<select>自体を丸ごと入れ替えます(disabled解除も同時にできる)。- サーバー(PHP)はGETパラメータ
categoryを受け取り、該当カテゴリのサブカテゴリ一覧を<option>として組み立てます。 - カテゴリが未選択/不正な値なら、
disabled付きのサブカテゴリ<select>を返して「選んでください」状態に戻します。
③ 依存セレクト(都道府県 → 市区町村)
都道府県を選ぶと、その都道府県に属する市区町村だけが候補に出る「依存セレクト」の例です。
JavaScriptなしで、下位の<select>だけを差し替えるので、フォームをシンプルに保てます。
HTML
<div class="DEMO">
<h4>③ 依存セレクト(都道府県 → 市区町村)</h4>
<form id="DEMO_DEP_SELECT_FORM" class="FORM">
<label>
都道府県
<select
id="DEMO_PREF"
name="pref"
hx-get="/htmx/demo/_dependent_select_3.php"
hx-trigger="change"
hx-target="#DEMO_CITY"
hx-swap="outerHTML"
>
<option value="">選択してください</option>
<option value="13">東京都</option>
<option value="27">大阪府</option>
<option value="23">愛知県</option>
</select>
</label>
<label>
市区町村
<!-- 初期状態は disabled。都道府県を選んだら、この<select>自体を差し替える -->
<select id="DEMO_CITY" name="city" disabled>
<option value="">都道府県を選択してください</option>
</select>
</label>
</form>
<p class="HTMX-NOTE">
都道府県を変更すると、市区町村の<select>だけが更新されます(ページ遷移なし)。
</p>
</div>
PHP
<?php
// 型を厳密に扱う(意図しない型変換を防ぐ)
declare(strict_types=1);
// 返すのはHTML(UTF-8)
header('Content-Type: text/html; charset=UTF-8');
// HTMLエスケープ用(XSS対策)
function h(string $s): string {
// 特殊文字を安全な文字列に変換する
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}
// 都道府県→市区町村(デモ用データ。実務ではDB/APIに置き換える)
$citiesMap = [
// 13: 東京都
'13' => [
'chiyoda' => '千代田区',
'chuo' => '中央区',
'shinjuku' => '新宿区',
'shibuya' => '渋谷区',
],
// 27: 大阪府
'27' => [
'osaka' => '大阪市',
'sakai' => '堺市',
'higashi' => '東大阪市',
'toyono' => '豊能町',
],
// 23: 愛知県
'23' => [
'nagoya' => '名古屋市',
'okazaki' => '岡崎市',
'ichinomiya' => '一宮市',
'tahara' => '田原市',
],
];
// GETパラメータ pref(都道府県コード)を受け取る(なければ空)
$pref = (string)($_GET['pref'] ?? '');
// 前後空白を除去する
$pref = trim($pref);
// 返す<select>の開始タグ(有効状態:disabledなし)
$selectOpen = '<select id="DEMO_CITY" name="city">';
// 都道府県が未選択、または未知の値なら「未選択状態」を返す
if ($pref === '' || !array_key_exists($pref, $citiesMap)) {
// disabled付きの<select>を出力する(初期状態)
echo '<select id="DEMO_CITY" name="city" disabled>';
// 案内オプションを出力する
echo '<option value="">都道府県を選択してください</option>';
// selectを閉じる
echo '</select>';
// ここで処理を終了する
exit;
}
// 該当都道府県の市区町村リスト(配列)を取り出す
$cities = $citiesMap[$pref];
// 有効な<select>(disabledなし)を出力開始
echo $selectOpen;
// 先頭の案内オプションを出力する
echo '<option value="">市区町村を選択してください</option>';
// 市区町村の配列を1件ずつ<option>にする
foreach ($cities as $value => $label) {
// value(送信値)を安全にエスケープする
$v = h((string)$value);
// label(表示名)を安全にエスケープする
$l = h((string)$label);
// optionタグを出力する
echo "<option value=\"{$v}\">{$l}</option>";
}
// selectを閉じる
echo '</select>';
デモ
③ 依存セレクト(都道府県 → 市区町村)
都道府県を変更すると、市区町村の<select>だけが更新されます(ページ遷移なし)。
解説
- 都道府県<select>に
hx-getを付け、選択が変わるたびにサーバーへGETリクエストを送ります。 hx-trigger="change"で、都道府県の変更タイミングでだけ発火します。hx-target="#DEMO_CITY"で、差し替え対象を「市区町村<select>」に限定します。hx-swap="outerHTML"で、市区町村<select>自体を丸ごと入れ替えます(disabled解除も同時にできる)。- サーバー(PHP)はGETパラメータ
prefを受け取り、該当都道府県の市区町村一覧を<option>として組み立てます。 - 都道府県が未選択/不正な値なら、
disabled付きの市区町村<select>を返して「選んでください」状態に戻します。
参考リンク
このページの著者
経験:Webアプリ/業務システム
得意:PHP・JavaScript・MySQL・CSS
個人実績:フォーム生成基盤/クイズ学習プラットフォーム 等
詳しいプロフィールはこちら! もちもちみかんのプロフィール