選択に応じて候補が変わる連動プルダウン
大分類の選択に応じて、次のプルダウンの候補が変わる連動ドロップダウンの作り方を3段階で解説します。各デモと同じ動きを再現できるHTML・CSS・JavaScriptを掲載しています。
HTML・CSS・JSコードプレビューツールでも動作を確認できます。
このページで作るもの
① 2段の連動 — 1つ目の選択で2つ目の候補が変わる
<div class="dyn-link-wrap">
<div class="dyn-link-field">
<label class="dyn-link-lbl">大分類:
<select class="dyn-link-picker dyn-link-main">
<option>選択してください</option>
</select>
</label>
</div>
<div class="dyn-link-field">
<label class="dyn-link-lbl">小分類:
<select class="dyn-link-picker dyn-link-sub">
<option>選択してください</option>
</select>
</label>
</div>
</div>
.dyn-link-wrap {
display: flex;
flex-direction: column;
gap: 14px;
}
.dyn-link-lbl {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 600;
color: #334155;
}
.dyn-link-picker {
flex: 1;
min-width: 0;
height: 34px;
padding: 6px 10px;
border: 1px solid #cbd5e1;
border-radius: 6px;
font-size: 14px;
background: #fff;
color: #334155;
}
var mainItems = [
'エコグッズ',
'衛生グッズ',
'防災グッズ'
];
var subItems = [
{group: 'エコグッズ', label: '天然素材'},
{group: 'エコグッズ', label: 'リサイクル素材'},
{group: '衛生グッズ', label: 'マスク'},
{group: '衛生グッズ', label: '除菌'},
{group: '防災グッズ', label: 'セット物'},
{group: '防災グッズ', label: 'ライト'}
];
var mainPick = document.querySelector('.dyn-link-main');
var subPick = document.querySelector('.dyn-link-sub');
var placeholder = '選択してください';
function makeOpt(txt) {
var opt = document.createElement('option');
opt.textContent = txt;
return opt;
}
function clearOpts(picker) {
while (picker.firstChild) {
picker.removeChild(picker.firstChild);
}
}
mainItems.forEach(function(item) {
mainPick.appendChild(makeOpt(item));
});
mainPick.addEventListener('input', function() {
clearOpts(subPick);
subPick.appendChild(makeOpt(placeholder));
subItems.forEach(function(item) {
if (mainPick.value === item.group) {
subPick.appendChild(makeOpt(item.label));
}
});
});
大分類の変更を input で検知し、小分類の option をいったん空にしてから該当分だけ足し直します。紐づけはオブジェクトの group プロパティで行い、if 1本で絞り込めます。appendChild するまで画面には出ません。
② 未選択時はロック — disabled で操作を止める
<div class="dyn-lock-wrap">
<div class="dyn-lock-field">
<label class="dyn-lock-lbl">大分類:
<select class="dyn-lock-picker dyn-lock-main">
<option>選択してください</option>
</select>
</label>
</div>
<div class="dyn-lock-field">
<label class="dyn-lock-lbl">小分類:
<select class="dyn-lock-picker dyn-lock-sub" disabled>
<option>選択してください</option>
</select>
</label>
</div>
</div>
.dyn-lock-wrap {
display: flex;
flex-direction: column;
gap: 14px;
}
.dyn-lock-lbl {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 600;
color: #334155;
}
.dyn-lock-picker {
flex: 1;
min-width: 0;
height: 34px;
padding: 6px 10px;
border: 1px solid #cbd5e1;
border-radius: 6px;
font-size: 14px;
background: #fff;
color: #334155;
}
.dyn-lock-picker:disabled {
background: #f1f5f9;
color: #94a3b8;
cursor: not-allowed;
border-color: #e2e8f0;
}
var mainItems = [
'エコグッズ',
'衛生グッズ',
'防災グッズ'
];
var subItems = [
{group: 'エコグッズ', label: '天然素材'},
{group: 'エコグッズ', label: 'リサイクル素材'},
{group: '衛生グッズ', label: 'マスク'},
{group: '衛生グッズ', label: '除菌'},
{group: '防災グッズ', label: 'セット物'},
{group: '防災グッズ', label: 'ライト'}
];
var mainPick = document.querySelector('.dyn-lock-main');
var subPick = document.querySelector('.dyn-lock-sub');
var placeholder = '選択してください';
function makeOpt(txt) {
var opt = document.createElement('option');
opt.textContent = txt;
return opt;
}
function clearOpts(picker) {
while (picker.firstChild) {
picker.removeChild(picker.firstChild);
}
}
mainItems.forEach(function(item) {
mainPick.appendChild(makeOpt(item));
});
mainPick.addEventListener('input', function() {
clearOpts(subPick);
subPick.appendChild(makeOpt(placeholder));
subPick.disabled = false;
if (mainPick.value === placeholder) {
subPick.disabled = true;
return;
}
subItems.forEach(function(item) {
if (mainPick.value === item.group) {
subPick.appendChild(makeOpt(item.label));
}
});
});
最初から小分類に disabled を付けておき、大分類が選ばれたら false にします。プレースホルダへ戻ったら再び true にすると、まだ選べない段階での誤操作を防げます。
③ 3段連動 — 大分類 → 小分類 → 商品名
<div class="dyn-chain-wrap">
<div class="dyn-chain-field">
<label class="dyn-chain-lbl">大分類:
<select class="dyn-chain-picker dyn-chain-main">
<option>選択してください</option>
</select>
</label>
</div>
<div class="dyn-chain-field">
<label class="dyn-chain-lbl">小分類:
<select class="dyn-chain-picker dyn-chain-sub" disabled>
<option>選択してください</option>
</select>
</label>
</div>
<div class="dyn-chain-field">
<label class="dyn-chain-lbl">商品名:
<select class="dyn-chain-picker dyn-chain-item" disabled>
<option>選択してください</option>
</select>
</label>
</div>
</div>
.dyn-chain-wrap {
display: flex;
flex-direction: column;
gap: 14px;
}
.dyn-chain-lbl {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 600;
color: #334155;
}
.dyn-chain-picker {
flex: 1;
min-width: 0;
height: 34px;
padding: 6px 10px;
border: 1px solid #cbd5e1;
border-radius: 6px;
font-size: 14px;
background: #fff;
color: #334155;
}
.dyn-chain-picker:disabled {
background: #f1f5f9;
color: #94a3b8;
cursor: not-allowed;
border-color: #e2e8f0;
}
var mainItems = [
'エコグッズ',
'衛生グッズ',
'防災グッズ'
];
var subItems = [
{group: 'エコグッズ', label: '天然素材'},
{group: 'エコグッズ', label: 'リサイクル素材'},
{group: '衛生グッズ', label: 'マスク'},
{group: '衛生グッズ', label: '除菌'},
{group: '防災グッズ', label: 'セット物'},
{group: '防災グッズ', label: 'ライト'}
];
var itemList = [
{group: '天然素材', label: 'バンブーマグカップ'},
{group: '天然素材', label: 'リユースコーヒータンブラー'},
{group: 'リサイクル素材', label: 'リサイクルコットンバッグ'},
{group: 'リサイクル素材', label: '再生PETブランケット'},
{group: 'マスク', label: '不織布マスク'},
{group: 'マスク', label: 'ウレタンマスク'},
{group: '除菌', label: '除菌スプレー'},
{group: '除菌', label: '除菌ウェットティッシュ'},
{group: 'セット物', label: '防災10点セット'},
{group: 'セット物', label: '防災20点セット'},
{group: 'ライト', label: 'LED電灯'},
{group: 'ライト', label: 'ランタン'}
];
var mainPick = document.querySelector('.dyn-chain-main');
var subPick = document.querySelector('.dyn-chain-sub');
var itemPick = document.querySelector('.dyn-chain-item');
var placeholder = '選択してください';
function makeOpt(txt) {
var opt = document.createElement('option');
opt.textContent = txt;
return opt;
}
function clearOpts(picker) {
while (picker.firstChild) {
picker.removeChild(picker.firstChild);
}
}
function resetPicker(picker, locked) {
clearOpts(picker);
picker.appendChild(makeOpt(placeholder));
picker.disabled = locked;
}
mainItems.forEach(function(item) {
mainPick.appendChild(makeOpt(item));
});
mainPick.addEventListener('input', function() {
resetPicker(subPick, false);
resetPicker(itemPick, true);
if (mainPick.value === placeholder) {
subPick.disabled = true;
return;
}
subItems.forEach(function(item) {
if (mainPick.value === item.group) {
subPick.appendChild(makeOpt(item.label));
}
});
});
subPick.addEventListener('input', function() {
resetPicker(itemPick, false);
if (subPick.value === placeholder) {
itemPick.disabled = true;
return;
}
itemList.forEach(function(item) {
if (subPick.value === item.group) {
itemPick.appendChild(makeOpt(item.label));
}
});
});
3段では、大分類を変えたときに小分類だけでなく商品名も空に戻し、disabled にします。古い候補が残ると誤選択の原因になるため、「上位変更 → 下位リセット」が多段連動の定石です。共通処理は関数にまとめると読みやすくなります。
select や id を含めないでください。コピペ用コードではクラス名で要素を取り、pick 系の名前にしています。また、候補データは配列のままでも動きますが、件数が増えるとネストしたオブジェクトの方が管理しやすいです。
input と change、どちらを使えばいいですか?
どちらでも連動は作れます。このページは input を使っています。キーボード操作も含めて値が変わったタイミングで動かしたいときに向いています。ボタン確定後だけ動かしたい場合は change でも構いません。
大分類を変えたのに古い小分類が残ります
上位の変更時に下位の option を空にしてから作り直してください。③のように、さらに下の段もまとめてリセットすると安心です。
最初から小分類を選べないようにしたい
②のとおり、HTML に disabled を付けておき、大分類が選ばれたら disabled = false にします。プレースホルダへ戻したら再び true にします。
データの持ち方のおすすめは?
初心者向けには「親の名前」と「表示ラベル」を持つオブジェクト配列が分かりやすいです。サーバーから JSON で受け取る場合も、同じ形に整形してから option を生成すると扱いやすいです。
当サイトで公開しているWebデザインやUIの実装例は、一覧として以下記事に纏めています。

