デザインの様式を、動くデモ・効く理由・実装で踏んだ罠つきで解剖する図鑑。
様式名: トーンオントーン / アコーディオン
「壊れた例」を押すと、この様式が成立しなくなる条件を実物で見られます。
| 対象 | 文字色 | 背景 | 実効背景 | 比 | 判定 |
|---|---|---|---|---|---|
| 見出し・質問文(白パネル) | #1e293b | #ffffff | #ffffff | 14.63:1 | AA 合格 |
| 本文(白パネル) | #334155 | #ffffff | #ffffff | 10.35:1 | AA 合格 |
| 補足文(地 #eef2f7) | #556072 | #eef2f7 | #eef2f7 | 5.66:1 | AA 合格 |
| シェブロン(非文字3:1基準・白地) | #334e68 | #ffffff | #ffffff | 8.64:1 | AA 合格 |
| パネル境界(採用値・非文字3:1基準) | #8593a8 | #ffffff | #ffffff | 3.12:1 | AA 合格 |
| 壊れた例:薄いパネル境界(非文字) | #c3ccd9 | #ffffff | #ffffff | 1.62:1 | 不合格 |
<!doctype html>
<html lang="ja"><head><meta charset="utf-8"><title>FAQアコーディオン デモ</title><style>
*{box-sizing:border-box;margin:0;padding:0}
:root{
/* トーンオントーン:スレート単色の濃淡でまとめる */
--bg:#eef2f7; --panel:#ffffff; --ink:#1e293b; --body:#334155; --sub:#556072;
--line:#8593a8; --accent:#334e68;
}
body{background:var(--bg);color:var(--ink);min-height:340px;padding:26px 24px;
font-family:system-ui,-apple-system,"Hiragino Kaku Gothic ProN","Yu Gothic",sans-serif}
.head{margin:0 auto 16px;max-width:560px}
.head h3{font-size:18px;font-weight:700;letter-spacing:.02em}
.head p{font-size:12px;color:var(--sub);margin-top:3px}
.acc{max-width:560px;margin:0 auto;display:flex;flex-direction:column;gap:10px}
.item{background:var(--panel);border:1px solid var(--line);border-radius:12px;overflow:hidden}
/* 開閉トリガは本物の button。全幅・44px 以上・キーボードで操作できる */
.q{width:100%;display:flex;align-items:center;gap:12px;background:none;border:0;
cursor:pointer;text-align:left;padding:15px 16px;min-height:52px;
font:inherit;font-size:14px;font-weight:600;color:var(--ink)}
.q .txt{flex:1}
/* シェブロンは形(回転)で開閉を示す。色だけに頼らない */
.q .chev{width:16px;height:16px;flex:none;color:var(--accent);transition:transform .2s ease}
.q[aria-expanded="true"] .chev{transform:rotate(180deg)}
.q:focus-visible{outline:3px solid var(--accent);outline-offset:-3px}
.q:hover{background:#f4f7fb}
.panel{overflow:hidden;transition:height .22s ease}
.panel .inner{padding:0 16px 16px;font-size:13px;line-height:1.85;color:var(--body)}
.panel[hidden]{display:none}
@media(prefers-reduced-motion:reduce){
.q .chev,.panel{transition:none}
}
</style></head><body>
<div class="head"><h3>よくある質問</h3><p>開閉は見出しをクリック/Enter・Space キーでも操作できます</p></div>
<div class="acc" id="acc">
<div class="item">
<button class="q" aria-expanded="true" aria-controls="p1" id="q1">
<span class="txt">無料プランでどこまで使えますか?</span>
<svg class="chev" viewBox="0 0 16 16" aria-hidden="true"><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M3 6l5 5 5-5"/></svg>
</button>
<div class="panel" id="p1" role="region" aria-labelledby="q1">
<div class="inner">プロジェクトを3件まで作成でき、基本のエクスポートが使えます。クレジットカードの登録は不要で、期限もありません。まず試したい方に向いています。</div>
</div>
</div>
<div class="item">
<button class="q" aria-expanded="false" aria-controls="p2" id="q2">
<span class="txt">プランはいつでも変更できますか?</span>
<svg class="chev" viewBox="0 0 16 16" aria-hidden="true"><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M3 6l5 5 5-5"/></svg>
</button>
<div class="panel" id="p2" role="region" aria-labelledby="q2" hidden>
<div class="inner">はい。管理画面からいつでもアップグレード・ダウングレードできます。差額は日割りで精算され、解約後もその請求期間の終わりまでは利用できます。</div>
</div>
</div>
<div class="item">
<button class="q" aria-expanded="false" aria-controls="p3" id="q3">
<span class="txt">データのエクスポート形式は?</span>
<svg class="chev" viewBox="0 0 16 16" aria-hidden="true"><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M3 6l5 5 5-5"/></svg>
</button>
<div class="panel" id="p3" role="region" aria-labelledby="q3" hidden>
<div class="inner">PNG・SVG・PDF に対応しています。有料プランでは高解像度書き出しと、複数ファイルの一括エクスポートが利用できます。</div>
</div>
</div>
</div>
<script>
const reduce = matchMedia('(prefers-reduced-motion:reduce)').matches;
document.querySelectorAll('.q').forEach(q=>{
const panel = document.getElementById(q.getAttribute('aria-controls'));
q.addEventListener('click', ()=>{
const open = q.getAttribute('aria-expanded')==='true';
q.setAttribute('aria-expanded', String(!open));
if(open){
if(reduce){ panel.hidden=true; return; }
panel.style.height = panel.scrollHeight+'px';
requestAnimationFrame(()=>{ panel.style.height='0px'; });
panel.addEventListener('transitionend', ()=>{ panel.hidden=true; panel.style.height=''; }, {once:true});
}else{
panel.hidden=false;
if(reduce) return;
const h = panel.scrollHeight;
panel.style.height='0px';
requestAnimationFrame(()=>{ panel.style.height=h+'px'; });
panel.addEventListener('transitionend', ()=>{ panel.style.height=''; }, {once:true});
}
});
});
</script>
</body></html>