FAQアコーディオン(開閉ディスクロージャ)

様式名: トーンオントーン / アコーディオン

アコーディオンFAQ開閉ディスクロージャアクセシビリティ
⧉ 別タブで開く

「壊れた例」を押すと、この様式が成立しなくなる条件を実物で見られます。

特徴 — なぜ効くか

  • スレート単色の濃淡でまとめるトーンオントーン。質問を畳んで一覧性を保つ部位
  • 開閉トリガは本物の <button>。マウスでもキーボード(Enter/Space)でも操作できる
  • 開閉状態はシェブロンの回転+ aria-expanded で示す。色だけに頼らない
  • パネルは aria-controls/role=region で見出しに結びつけ、読み上げでも構造が分かる
  • 見た目より、キーボード操作・状態通知・境界コントラストといった実装の正しさが価値になる部位

実装メモ — 踏んだ罠

  • 開閉トリガは <div onclick> ではなく <button> にする。button はネイティブで Tab フォーカス・Enter/Space 起動・読み上げ対応が付いてくる。div だと全部自前で足す必要があり、抜けやすい
  • 状態は button の aria-expanded="true|false" で持ち、パネルを aria-controls で指す。パネル側に role=region と aria-labelledby を付けると、読み上げで「どの質問の領域か」が分かる
  • 開閉インジケータは色だけにしない。シェブロン(V字)を回転させ、形の変化で開閉を示す。aria-expanded があるので支援技術にも同時に伝わる
  • 【自分で実測した数値】白パネルで見出し #1e293b 14.63:1/本文 #334155 10.35:1/サブ #556072 は地 #eef2f7 上で 5.66:1。全て AA を通す
  • 【自分で実測した数値・非文字】パネルの境界線は白地との差が小さいと消える。薄い #c3ccd9 は 1.62:1 で 1.4.11 未達。#8593a8 なら 3.12:1 で通る。シェブロン #334e68 は 8.64:1
  • 高さアニメは scrollHeight を測って height を 0↔実寸で補間する。prefers-reduced-motion のときはアニメを外し即時に開閉する(WCAG 2.3.3)。タップ領域はトリガに min-height 52px を確保(2.5.8)

使いどころ / 使ってはいけない場面

壊れる条件

コントラスト実測

対象文字色背景実効背景判定
見出し・質問文(白パネル)#1e293b#ffffff#ffffff14.63:1AA 合格
本文(白パネル)#334155#ffffff#ffffff10.35:1AA 合格
補足文(地 #eef2f7)#556072#eef2f7#eef2f75.66:1AA 合格
シェブロン(非文字3:1基準・白地)#334e68#ffffff#ffffff8.64:1AA 合格
パネル境界(採用値・非文字3:1基準)#8593a8#ffffff#ffffff3.12:1AA 合格
壊れた例:薄いパネル境界(非文字)#c3ccd9#ffffff#ffffff1.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>
出典・参考: 自作の再現実装。開閉トリガのボタン化・状態通知・非文字コントラストは WCAG 2.1.1/4.1.2/1.4.11 に基づく。コントラスト値は当方で実測。