戦え、エンジニア諸君!!
「<select>
だとデザインが制限される…」 — そんな悩みをネイティブ要素の組み合わせだけで解決する実装 Tips を紹介する!
キモはラジオボタン+input[type="text"]
+CSS。
―― 今回は動くサンプルを丸ごと貼りつつ、構造・挙動・アクセシビリティまで一気に押さえるッ!!
Contents
🔧 1. サンプル全体
まずは完成版コードをどうぞ。コピペ即動作!
<!-- HTML部分抜粋 -->
<div class="select-wrapper">
<label for="selectInput1">
<input type="text" id="selectInput1" readonly placeholder="選択してください">
</label>
<div class="radio-dropdown">
<label class="radio-option">
<input type="radio" name="option1" value="オプション1-1">
<span>オプション1-1</span>
</label>
...
</div>
</div>
(全文コードは記事末に貼付)
💡 2. 仕組みのポイント
- 入力欄=ただの
<input type="text" readonly>
→実際にデータを送るのはラジオ。テキストは「表示用」キャッシュ。 - 選択肢はラジオ+
<span>
→クリック領域を広げ、CSSで<span>
を option 風に装飾。 - 開閉制御は
.open
クラス
→クリックで付替え、矢印の回転も ::after で演出。
🛠️ 3. 改造ポイント(UXアップ)
- Esc キーで閉じる
document.addEventListener('keydown', e => { if(e.key==='Escape') closeAll(); });
- ARIA 属性でスクリーンリーダー対応
role="combobox"
&aria-expanded
を付与。 - キーボード操作 上下矢印で次の
.radio-option
に focus →space
で選択。
🚨 4. 注意点
- フォーム送信:実際にサーバへ飛ぶのはラジオ値。
バリデーションはrequired
をラジオに付与すると楽。 - 同名グループ:
name="option1"
/option2
… を忘れずに。 - クリックバブリング:ネストが深いと二重発火に要注意。
📜 全コード再掲(HTML + CSS + JS)
コード全文はこちら
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multiple Custom Radio Dropdowns</title>
<style>
.select-wrapper {
position: relative;
display: inline-block;
width: 200px;
margin: 10px;
}
/* 入力フィールドをselect風に */
input[type="text"] {
width: 100%;
padding: 8px 24px 8px 8px; /* 右側に矢印のスペース */
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
background: #fff;
cursor: pointer;
box-sizing: border-box;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
}
/* ラジオボタンのコンテナをselectのドロップダウン風に */
.radio-dropdown {
display: none;
position: absolute;
top: 100%;
left: 0;
width: 100%;
background: #fff;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
z-index: 10;
padding: 0;
max-height: 200px; /* スクロール可能にする */
overflow-y: auto;
}
.radio-dropdown.open {
display: block;
}
/* ラジオボタンを非表示 */
.radio-option input[type="radio"] {
display: none;
}
/* ラジオオプションをselectのoption風に */
.radio-option {
margin: 0;
padding: 8px 12px;
cursor: pointer;
font-size: 16px;
color: #333;
}
/* ホバーと選択時のスタイル */
.radio-option:hover {
background: #f0f0f0;
}
.radio-option input[type="radio"]:checked + span {
background: #e0e0e0;
display: block;
}
/* spanでテキストを囲む */
.radio-option span {
display: block;
padding: 0;
}
/* カスタム矢印 */
.select-wrapper::after {
content: '';
position: absolute;
top: 50%;
right: 10px;
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid #333;
transform: translateY(-50%);
transition: transform 0.2s ease;
pointer-events: none;
}
.select-wrapper.open::after {
transform: translateY(-50%) rotate(180deg);
}
/* ラベルのハイライト */
.highlight {
background-color: yellow;
}
</style>
</head>
<body>
<!-- ドロップダウン1 -->
<div class="select-wrapper">
<label for="selectInput1">
<input type="text" id="selectInput1" readonly placeholder="選択してください">
</label>
<div class="radio-dropdown">
<label class="radio-option">
<input type="radio" name="option1" value="オプション1-1">
<span>オプション1-1</span>
</label>
<label class="radio-option">
<input type="radio" name="option1" value="オプション1-2">
<span>オプション1-2</span>
</label>
<label class="radio-option">
<input type="radio" name="option1" value="オプション1-3">
<span>オプション1-3</span>
</label>
</div>
</div>
<!-- ドロップダウン2 -->
<div class="select-wrapper">
<label for="selectInput2">
<input type="text" id="selectInput2" readonly placeholder="選択してください">
</label>
<div class="radio-dropdown">
<label class="radio-option">
<input type="radio" name="option2" value="オプション2-1">
<span>オプション2-1</span>
</label>
<label class="radio-option">
<input type="radio" name="option2" value="オプション2-2">
<span>オプション2-2</span>
</label>
<label class="radio-option">
<input type="radio" name="option2" value="オプション2-3">
<span>オプション2-3</span>
</label>
</div>
</div>
<script>
// すべての .select-wrapper を取得
const wrappers = document.querySelectorAll('.select-wrapper');
// 各ドロップダウンにイベントを設定
wrappers.forEach(wrapper => {
const input = wrapper.querySelector('input[type="text"]');
const dropdown = wrapper.querySelector('.radio-dropdown');
const radios = dropdown.querySelectorAll('input[type="radio"]');
const label = wrapper.querySelector('label');
// 入力フィールドをクリックしてドロップダウンを開閉
input.addEventListener('click', (event) => {
toggleDropdown(wrapper, dropdown, label);
event.stopPropagation();
});
// ラジオボタン選択時
radios.forEach(radio => {
radio.addEventListener('change', () => {
input.value = radio.value;
closeDropdown(wrapper, dropdown, label);
});
});
// ラベルをクリックしたときの動作
label.addEventListener('click', (event) => {
toggleDropdown(wrapper, dropdown, label);
event.stopPropagation();
});
// ラジオオプション(span)のクリックで選択
dropdown.querySelectorAll('.radio-option').forEach(option => {
option.addEventListener('click', (event) => {
const radio = option.querySelector('input[type="radio"]');
radio.checked = true;
radio.dispatchEvent(new Event('change'));
event.stopPropagation();
});
});
});
// ドキュメント全体のクリックでドロップダウンを閉じる
document.addEventListener('click', () => {
wrappers.forEach(wrapper => {
const dropdown = wrapper.querySelector('.radio-dropdown');
const label = wrapper.querySelector('label');
closeDropdown(wrapper, dropdown, label);
});
});
// ドロップダウンを開閉する関数
function toggleDropdown(wrapper, dropdown, label) {
if (dropdown.classList.contains('open')) {
closeDropdown(wrapper, dropdown, label);
} else {
// 他のドロップダウンをすべて閉じる
wrappers.forEach(otherWrapper => {
const otherDropdown = otherWrapper.querySelector('.radio-dropdown');
const otherLabel = otherWrapper.querySelector('label');
closeDropdown(otherWrapper, otherDropdown, otherLabel);
});
openDropdown(wrapper, dropdown, label);
}
}
// ドロップダウンを開く
function openDropdown(wrapper, dropdown, label) {
dropdown.classList.add('open');
wrapper.classList.add('open');
label.classList.add('highlight');
}
// ドロップダウンを閉じる
function closeDropdown(wrapper, dropdown, label) {
dropdown.classList.remove('open');
wrapper.classList.remove('open');
label.classList.remove('highlight');
}
</script>
</body>
</html>
📝 6. 覚悟のまとめ
- ネイティブ要素+CSS+JS少量で自由なUIは作れる
- select代替はアクセシビリティに目を光らせろ
- コードを分解→再構築する思考がUI実装力を育てる
📚 さらなる高みへ──団長推薦の学び

CSSシークレット ─ 47のテクニックで CSS をもっと強力に
擬似要素&フィルタで“ありえないUI”を実現するネタ帳。カスタムセレクトの発想源に!
※リンクはアフィリエイトを含みます。
🔥 団長の喝──コードを解体し、再構築せよ!
既成のタグに甘えるな。
原理を掴めば、UIは無限にデザインできる。
今日も一行、一スタイル、一イベントを研ぎ澄まし、
戦えッ! そして創れッ!!🔥🔥🔥