カスタムチェック / ラジオ
:has() を活用してチェックボックス・カードラジオ・トグルスイッチをCSSだけで装飾。アクセシブルな見た目のリッチな選択UIを構築できます。
ライブデモ
使用例(お題: カフェ MOON BREW)
この技法を「カフェ MOON BREW」というテーマのダミーサイトで実際に使った例です。
HTML
<div class="mb-order">
<header class="mb-order__head">
<span class="mb-logo">☕ MOON BREW</span>
<h2 class="mb-order__title">カフェラテ をカスタム</h2>
</header>
<form class="mb-order__form" novalidate>
<p class="cc-label">ミルクを選ぶ</p>
<!-- カードラジオ -->
<div class="cc-group" role="radiogroup">
<label class="cc-opt">
<input type="radio" name="milk" value="normal" checked>
<span class="cc-radio"></span>
<span class="cc-body"><strong>ミルク</strong><small>+¥0</small></span>
</label>
<label class="cc-opt">
<input type="radio" name="milk" value="oat">
<span class="cc-radio"></span>
<span class="cc-body"><strong>オーツ</strong><small>+¥60</small></span>
</label>
<label class="cc-opt">
<input type="radio" name="milk" value="soy">
<span class="cc-radio"></span>
<span class="cc-body"><strong>豆乳</strong><small>+¥60</small></span>
</label>
</div>
<p class="cc-label">トッピング</p>
<!-- カスタムチェック -->
<label class="cc-check">
<input type="checkbox" name="shot" checked>
<span class="cc-box"></span>
<span>エスプレッソ追加 +¥80</span>
</label>
<label class="cc-check">
<input type="checkbox" name="syrup">
<span class="cc-box"></span>
<span>キャラメルシロップ +¥50</span>
</label>
<!-- トグルスイッチ -->
<label class="cc-toggle">
<span>ホットで提供</span>
<input type="checkbox" name="hot" checked>
<span class="cc-switch"></span>
</label>
<button class="mb-cart" type="submit">カートに入れる ¥640</button>
</form>
</div>
CSS
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
background: radial-gradient(120% 100% at 50% 0%, #3a2817, #2b1d12);
color: #2b1d12;
}
.mb-order {
width: min(380px, 92vw);
padding: 22px 24px 24px;
background: #f5ede1;
border-radius: 18px;
box-shadow: 0 24px 56px -22px rgba(0, 0, 0, 0.6);
}
.mb-order__head { margin-bottom: 14px; }
.mb-logo { font-size: 0.78rem; font-weight: 700; letter-spacing: 0.08em; color: #a96e26; }
.mb-order__title { margin: 6px 0 0; font-size: 1.1rem; font-weight: 800; }
.cc-label {
margin: 14px 0 8px;
font-size: 0.74rem; font-weight: 700;
letter-spacing: 0.06em; color: #8a6c45;
}
/* カードラジオ:横並び */
.cc-group { display: flex; gap: 8px; }
.cc-opt {
flex: 1;
display: flex; align-items: center; gap: 7px;
padding: 9px 8px;
background: #fff;
border: 2px solid #e3d4ba;
border-radius: 11px;
cursor: pointer;
transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.cc-opt input { position: absolute; opacity: 0; pointer-events: none; }
.cc-radio {
width: 16px; height: 16px; flex: none;
border: 2px solid #c7ad86; border-radius: 50%;
display: grid; place-items: center;
transition: border-color 0.18s ease;
}
.cc-radio::after {
content: ""; width: 8px; height: 8px; border-radius: 50%;
background: #c98a3b; transform: scale(0);
transition: transform 0.18s ease;
}
.cc-body { display: flex; flex-direction: column; line-height: 1.2; }
.cc-body strong { font-size: 0.82rem; }
.cc-body small { font-size: 0.66rem; color: #9a7e57; }
/* :has() で選択中カードを強調(技法の主役) */
.cc-opt:has(input:checked) { border-color: #c98a3b; box-shadow: 0 6px 14px -8px rgba(201, 138, 59, 0.8); }
.cc-opt:has(input:checked) .cc-radio { border-color: #c98a3b; }
.cc-opt:has(input:checked) .cc-radio::after { transform: scale(1); }
/* カスタムチェック */
.cc-check {
display: flex; align-items: center; gap: 10px;
padding: 7px 2px; font-size: 0.85rem; cursor: pointer;
}
.cc-check input { position: absolute; opacity: 0; pointer-events: none; }
.cc-box {
width: 19px; height: 19px; flex: none;
border: 2px solid #c7ad86; border-radius: 6px;
display: grid; place-items: center;
transition: background 0.18s ease, border-color 0.18s ease;
}
.cc-box::after {
content: "✓"; color: #fff; font-size: 0.74rem; font-weight: 800;
transform: scale(0); transition: transform 0.16s ease;
}
.cc-check:has(input:checked) .cc-box { background: #c98a3b; border-color: #c98a3b; }
.cc-check:has(input:checked) .cc-box::after { transform: scale(1); }
/* トグルスイッチ */
.cc-toggle {
display: flex; align-items: center; justify-content: space-between;
margin: 6px 0 4px; padding: 8px 2px;
font-size: 0.85rem; cursor: pointer;
}
.cc-toggle input { position: absolute; opacity: 0; pointer-events: none; }
.cc-switch {
width: 42px; height: 23px; flex: none;
background: #d6c3a4; border-radius: 999px; position: relative;
transition: background 0.2s ease;
}
.cc-switch::after {
content: ""; position: absolute; top: 3px; left: 3px;
width: 17px; height: 17px; border-radius: 50%; background: #fff;
transition: transform 0.2s ease;
}
.cc-toggle:has(input:checked) .cc-switch { background: #c98a3b; }
.cc-toggle:has(input:checked) .cc-switch::after { transform: translateX(19px); }
.mb-cart {
width: 100%; margin-top: 12px; padding: 12px;
font-size: 0.92rem; font-weight: 700; letter-spacing: 0.04em;
color: #f5ede1;
background: linear-gradient(135deg, #c98a3b, #a96e26);
border: none; border-radius: 11px; cursor: pointer;
box-shadow: 0 12px 22px -10px rgba(169, 110, 38, 0.8);
transition: transform 0.15s ease;
}
.mb-cart:hover { transform: translateY(-2px); }
@media (prefers-reduced-motion: reduce) {
.cc-opt, .cc-radio, .cc-radio::after, .cc-box, .cc-box::after,
.cc-switch, .cc-switch::after, .mb-cart { transition: none; }
}
JavaScript
// 注文オプションに応じて合計金額を再計算(:has()装飾はCSSのみ)
const form = document.querySelector(".mb-order__form");
const cart = document.querySelector(".mb-cart");
if (form && cart) {
const BASE = 500; // カフェラテ基本価格
const milkPrice = { normal: 0, oat: 60, soy: 60 };
// 現在の選択から合計を求める
function total() {
const milk = form.querySelector('input[name="milk"]:checked');
let sum = BASE + (milk ? milkPrice[milk.value] : 0);
if (form.shot && form.shot.checked) sum += 80;
if (form.syrup && form.syrup.checked) sum += 50;
return sum;
}
// ボタン表示を更新
function refresh() {
cart.textContent = `カートに入れる ¥${total().toLocaleString("ja-JP")}`;
}
form.addEventListener("change", refresh);
form.addEventListener("submit", (e) => {
e.preventDefault(); // 実送信しない
const keep = cart.textContent;
cart.textContent = "カートに追加しました ✓";
cart.style.background = "linear-gradient(135deg,#3f7d4f,#5aa06a)";
setTimeout(() => {
cart.textContent = keep;
cart.style.background = "";
}, 1600);
});
refresh();
}
コード
HTML
<div class="stage">
<div class="cc-card">
<h2 class="cc-title">プランを選択</h2>
<!-- カスタムラジオ:プラン選択 -->
<div class="cc-group" role="radiogroup">
<label class="cc-opt">
<input type="radio" name="plan" value="free" checked>
<span class="cc-radio"></span>
<span class="cc-body"><strong>Free</strong><small>個人利用に</small></span>
</label>
<label class="cc-opt">
<input type="radio" name="plan" value="pro">
<span class="cc-radio"></span>
<span class="cc-body"><strong>Pro</strong><small>月額 ¥980</small></span>
</label>
<label class="cc-opt">
<input type="radio" name="plan" value="team">
<span class="cc-radio"></span>
<span class="cc-body"><strong>Team</strong><small>チーム向け</small></span>
</label>
</div>
<h3 class="cc-sub">オプション</h3>
<!-- カスタムチェック&トグル -->
<label class="cc-check">
<input type="checkbox" name="news" checked>
<span class="cc-box"></span>
<span>お知らせメールを受け取る</span>
</label>
<label class="cc-check">
<input type="checkbox" name="terms">
<span class="cc-box"></span>
<span>利用規約に同意する</span>
</label>
<label class="cc-toggle">
<span>ダークモード</span>
<input type="checkbox" name="dark">
<span class="cc-switch"></span>
</label>
</div>
</div>
CSS
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
background: linear-gradient(135deg, #eef2ff 0%, #e0e7ff 50%, #fae8ff 100%);
color: #1f2937;
}
.stage { width: 100%; padding: 22px; display: grid; place-items: center; }
.cc-card {
width: min(380px, 92vw);
padding: 26px 24px;
background: #fff;
border-radius: 18px;
box-shadow: 0 24px 56px -26px rgba(79, 70, 229, 0.4);
}
.cc-title { margin: 0 0 16px; font-size: 1.1rem; color: #1e293b; }
.cc-sub { margin: 22px 0 12px; font-size: 0.82rem; color: #64748b; letter-spacing: 0.04em; }
/* ラジオを横並びカードに */
.cc-group { display: flex; gap: 10px; }
.cc-opt {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 14px 8px;
border: 2px solid #e5e7eb;
border-radius: 12px;
cursor: pointer;
transition: border-color 0.18s ease, background 0.18s ease, transform 0.12s ease;
}
.cc-opt:hover { transform: translateY(-2px); }
.cc-opt input { position: absolute; opacity: 0; pointer-events: none; }
.cc-radio {
width: 18px; height: 18px;
border: 2px solid #cbd5e1;
border-radius: 50%;
position: relative;
transition: border-color 0.18s ease;
}
.cc-radio::after {
content: "";
position: absolute;
inset: 3px;
border-radius: 50%;
background: #6366f1;
transform: scale(0);
transition: transform 0.18s ease;
}
.cc-body { text-align: center; line-height: 1.3; }
.cc-body strong { display: block; font-size: 0.9rem; color: #1e293b; }
.cc-body small { font-size: 0.7rem; color: #94a3b8; }
/* チェック中のラジオカード */
.cc-opt:has(input:checked) { border-color: #6366f1; background: #eef2ff; }
.cc-opt:has(input:checked) .cc-radio { border-color: #6366f1; }
.cc-opt:has(input:checked) .cc-radio::after { transform: scale(1); }
.cc-opt input:focus-visible ~ .cc-radio { box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.22); }
/* カスタムチェックボックス */
.cc-check {
display: flex; align-items: center; gap: 11px;
margin-bottom: 12px;
font-size: 0.88rem;
cursor: pointer;
}
.cc-check input { position: absolute; opacity: 0; pointer-events: none; }
.cc-box {
width: 22px; height: 22px; flex: none;
border: 2px solid #cbd5e1;
border-radius: 7px;
position: relative;
transition: background 0.18s ease, border-color 0.18s ease;
}
.cc-box::after {
content: "";
position: absolute;
left: 6.5px; top: 2.5px;
width: 5px; height: 10px;
border: solid #fff;
border-width: 0 2.5px 2.5px 0;
transform: rotate(45deg) scale(0);
transition: transform 0.18s ease;
}
.cc-check:has(input:checked) .cc-box { background: #6366f1; border-color: #6366f1; }
.cc-check:has(input:checked) .cc-box::after { transform: rotate(45deg) scale(1); }
.cc-check input:focus-visible ~ .cc-box { box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.22); }
/* トグルスイッチ */
.cc-toggle {
display: flex; align-items: center; justify-content: space-between;
margin-top: 16px;
font-size: 0.88rem;
cursor: pointer;
}
.cc-toggle input { position: absolute; opacity: 0; pointer-events: none; }
.cc-switch {
width: 46px; height: 26px;
background: #cbd5e1;
border-radius: 999px;
position: relative;
transition: background 0.22s ease;
}
.cc-switch::after {
content: "";
position: absolute;
top: 3px; left: 3px;
width: 20px; height: 20px;
background: #fff;
border-radius: 50%;
box-shadow: 0 2px 5px rgba(0,0,0,0.25);
transition: transform 0.22s ease;
}
.cc-toggle:has(input:checked) .cc-switch { background: #6366f1; }
.cc-toggle:has(input:checked) .cc-switch::after { transform: translateX(20px); }
.cc-toggle input:focus-visible ~ .cc-switch { box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.22); }
@media (prefers-reduced-motion: reduce) {
.cc-opt, .cc-radio, .cc-radio::after, .cc-box, .cc-box::after, .cc-switch, .cc-switch::after { transition: none; }
}
JavaScript
// ダークモードのトグルでカード配色を切り替えるデモ
const card = document.querySelector(".cc-card");
const dark = document.querySelector('input[name="dark"]');
if (card && dark) {
dark.addEventListener("change", () => {
if (dark.checked) {
card.style.background = "#1e293b";
card.style.color = "#e2e8f0";
} else {
card.style.background = "";
card.style.color = "";
}
});
}
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「カスタムチェック / ラジオ」の効果を追加してください。
# 追加してほしい効果
カスタムチェック / ラジオ(フォーム & 入力)
:has() を活用してチェックボックス・カードラジオ・トグルスイッチをCSSだけで装飾。アクセシブルな見た目のリッチな選択UIを構築できます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<div class="stage">
<div class="cc-card">
<h2 class="cc-title">プランを選択</h2>
<!-- カスタムラジオ:プラン選択 -->
<div class="cc-group" role="radiogroup">
<label class="cc-opt">
<input type="radio" name="plan" value="free" checked>
<span class="cc-radio"></span>
<span class="cc-body"><strong>Free</strong><small>個人利用に</small></span>
</label>
<label class="cc-opt">
<input type="radio" name="plan" value="pro">
<span class="cc-radio"></span>
<span class="cc-body"><strong>Pro</strong><small>月額 ¥980</small></span>
</label>
<label class="cc-opt">
<input type="radio" name="plan" value="team">
<span class="cc-radio"></span>
<span class="cc-body"><strong>Team</strong><small>チーム向け</small></span>
</label>
</div>
<h3 class="cc-sub">オプション</h3>
<!-- カスタムチェック&トグル -->
<label class="cc-check">
<input type="checkbox" name="news" checked>
<span class="cc-box"></span>
<span>お知らせメールを受け取る</span>
</label>
<label class="cc-check">
<input type="checkbox" name="terms">
<span class="cc-box"></span>
<span>利用規約に同意する</span>
</label>
<label class="cc-toggle">
<span>ダークモード</span>
<input type="checkbox" name="dark">
<span class="cc-switch"></span>
</label>
</div>
</div>
【CSS】
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
background: linear-gradient(135deg, #eef2ff 0%, #e0e7ff 50%, #fae8ff 100%);
color: #1f2937;
}
.stage { width: 100%; padding: 22px; display: grid; place-items: center; }
.cc-card {
width: min(380px, 92vw);
padding: 26px 24px;
background: #fff;
border-radius: 18px;
box-shadow: 0 24px 56px -26px rgba(79, 70, 229, 0.4);
}
.cc-title { margin: 0 0 16px; font-size: 1.1rem; color: #1e293b; }
.cc-sub { margin: 22px 0 12px; font-size: 0.82rem; color: #64748b; letter-spacing: 0.04em; }
/* ラジオを横並びカードに */
.cc-group { display: flex; gap: 10px; }
.cc-opt {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 14px 8px;
border: 2px solid #e5e7eb;
border-radius: 12px;
cursor: pointer;
transition: border-color 0.18s ease, background 0.18s ease, transform 0.12s ease;
}
.cc-opt:hover { transform: translateY(-2px); }
.cc-opt input { position: absolute; opacity: 0; pointer-events: none; }
.cc-radio {
width: 18px; height: 18px;
border: 2px solid #cbd5e1;
border-radius: 50%;
position: relative;
transition: border-color 0.18s ease;
}
.cc-radio::after {
content: "";
position: absolute;
inset: 3px;
border-radius: 50%;
background: #6366f1;
transform: scale(0);
transition: transform 0.18s ease;
}
.cc-body { text-align: center; line-height: 1.3; }
.cc-body strong { display: block; font-size: 0.9rem; color: #1e293b; }
.cc-body small { font-size: 0.7rem; color: #94a3b8; }
/* チェック中のラジオカード */
.cc-opt:has(input:checked) { border-color: #6366f1; background: #eef2ff; }
.cc-opt:has(input:checked) .cc-radio { border-color: #6366f1; }
.cc-opt:has(input:checked) .cc-radio::after { transform: scale(1); }
.cc-opt input:focus-visible ~ .cc-radio { box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.22); }
/* カスタムチェックボックス */
.cc-check {
display: flex; align-items: center; gap: 11px;
margin-bottom: 12px;
font-size: 0.88rem;
cursor: pointer;
}
.cc-check input { position: absolute; opacity: 0; pointer-events: none; }
.cc-box {
width: 22px; height: 22px; flex: none;
border: 2px solid #cbd5e1;
border-radius: 7px;
position: relative;
transition: background 0.18s ease, border-color 0.18s ease;
}
.cc-box::after {
content: "";
position: absolute;
left: 6.5px; top: 2.5px;
width: 5px; height: 10px;
border: solid #fff;
border-width: 0 2.5px 2.5px 0;
transform: rotate(45deg) scale(0);
transition: transform 0.18s ease;
}
.cc-check:has(input:checked) .cc-box { background: #6366f1; border-color: #6366f1; }
.cc-check:has(input:checked) .cc-box::after { transform: rotate(45deg) scale(1); }
.cc-check input:focus-visible ~ .cc-box { box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.22); }
/* トグルスイッチ */
.cc-toggle {
display: flex; align-items: center; justify-content: space-between;
margin-top: 16px;
font-size: 0.88rem;
cursor: pointer;
}
.cc-toggle input { position: absolute; opacity: 0; pointer-events: none; }
.cc-switch {
width: 46px; height: 26px;
background: #cbd5e1;
border-radius: 999px;
position: relative;
transition: background 0.22s ease;
}
.cc-switch::after {
content: "";
position: absolute;
top: 3px; left: 3px;
width: 20px; height: 20px;
background: #fff;
border-radius: 50%;
box-shadow: 0 2px 5px rgba(0,0,0,0.25);
transition: transform 0.22s ease;
}
.cc-toggle:has(input:checked) .cc-switch { background: #6366f1; }
.cc-toggle:has(input:checked) .cc-switch::after { transform: translateX(20px); }
.cc-toggle input:focus-visible ~ .cc-switch { box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.22); }
@media (prefers-reduced-motion: reduce) {
.cc-opt, .cc-radio, .cc-radio::after, .cc-box, .cc-box::after, .cc-switch, .cc-switch::after { transition: none; }
}
【JavaScript】
// ダークモードのトグルでカード配色を切り替えるデモ
const card = document.querySelector(".cc-card");
const dark = document.querySelector('input[name="dark"]');
if (card && dark) {
dark.addEventListener("change", () => {
if (dark.checked) {
card.style.background = "#1e293b";
card.style.color = "#e2e8f0";
} else {
card.style.background = "";
card.style.color = "";
}
});
}
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。