モーダルダイアログ(確認)

様式名: ソフト・グラデーション / モーダル

モーダルダイアログ確認フォーカス管理アクセシビリティ
⧉ 別タブで開く

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

特徴 — なぜ効くか

  • 淡いグラデの見出し帯+白い本文で、操作を止めて注意を1点に集める部位
  • role=dialog/aria-modal で『いまダイアログ内にいる』ことを支援技術に伝える
  • 開くとフォーカスがダイアログに移り、Tab は内側だけを巡回する(フォーカストラップ)
  • Esc キー・背景クリック・閉じるボタンで閉じ、フォーカスは開いたトリガに戻る
  • 見た目より、フォーカス移動・トラップ・復帰・状態通知といった実装の正しさが価値になる部位

実装メモ — 踏んだ罠

  • コンテナは role="dialog" aria-modal="true" にし、aria-labelledby で見出し、aria-describedby で本文を指す。これで読み上げが『ダイアログ・見出し・説明』を順に伝える
  • 開いたら必ずフォーカスをダイアログ内へ移す(先頭の操作ボタン等)。移さないと、キーボード利用者は背後にフォーカスが残り、開いたことに気づけない
  • フォーカストラップ:Tab/Shift+Tab が先頭↔末尾で折り返すよう keydown で制御する。閉じるときは開く前のトリガ要素へフォーカスを戻す(どこにいたか見失わせない)
  • 閉じる手段を3つ用意する:閉じる button(aria-label="閉じる")・Esc キー・背景スクリムのクリック。× は <span> でなく <button> にする
  • 【自分で実測した数値】白本文で見出し #1e293b 12.44:1(淡グラデ帯の明部 #fde7f0 上)/本文 #3f4a5c 8.95:1/削除ボタン 白文字 on #b42318 6.57:1。ゴースト枠 #8593a8 は 3.12:1 で非文字3:1を満たす。全て AA を通す
  • 背景スクリムは rgba(15,23,42,.55) で下を暗く落とし、モーダルとの前後を明確にする。開いている間は背後のスクロールを止める(body に overflow:hidden)

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

壊れる条件

コントラスト実測

対象文字色背景実効背景判定
見出し(淡グラデ帯・明部)#1e293b#fde7f0#fde7f012.44:1AA 合格
本文(白)#3f4a5c#ffffff#ffffff8.95:1AA 合格
削除ボタン 白文字(赤地)#ffffff#b42318#b423186.57:1AA 合格
ゴースト枠(非文字3:1基準・白地)#8593a8#ffffff#ffffff3.12:1AA 合格
背景説明文(淡グラデ地)#5b6676#f7eef6#f7eef65.13:1AA 合格
壊れた例:薄い×文字#94a3b8#f4e9f2#f4e9f22.17:1不合格

アクセシビリティ

スマホへの落とし方

ブラウザ対応

関連するパーツ

コードを見る
<!doctype html>
<html lang="ja"><head><meta charset="utf-8"><title>モーダルダイアログ デモ</title><style>
*{box-sizing:border-box;margin:0;padding:0}
:root{--ink:#1e293b; --body:#3f4a5c; --sub:#5b6676; --line:#8593a8;
  --accent:#4338ca; --danger:#b42318;}
body{min-height:340px;padding:26px 24px;color:var(--ink);
  font-family:system-ui,-apple-system,"Hiragino Kaku Gothic ProN","Yu Gothic",sans-serif;
  /* ソフト・グラデーション:淡い地 */
  background:linear-gradient(135deg,#eef2fb 0%,#f7eef6 50%,#eef6f4 100%)}
.page{max-width:560px;margin:0 auto}
.page h3{font-size:17px;font-weight:700}
.page p{font-size:13px;color:var(--sub);margin:6px 0 16px;line-height:1.7}
.open-btn{font:inherit;font-size:13px;font-weight:700;color:#fff;background:var(--danger);
  border:0;border-radius:9px;padding:11px 18px;min-height:44px;cursor:pointer}
.open-btn:focus-visible{outline:3px solid var(--danger);outline-offset:2px}
/* 背景スクリム:ページを暗く落として奥行きと集中を作る */
.scrim{position:fixed;inset:0;background:rgba(15,23,42,.55);
  display:flex;align-items:center;justify-content:center;padding:20px}
.scrim[hidden]{display:none}
/* モーダル本体:淡いグラデ見出し帯+白い本文。文字は必ず濃色 */
.modal{width:100%;max-width:400px;background:#fff;border-radius:16px;overflow:hidden;
  box-shadow:0 24px 60px rgba(15,23,42,.4)}
.modal .bar{background:linear-gradient(120deg,#fde7f0,#e8edfb);padding:18px 22px;
  display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
.modal .bar h2{font-size:16px;font-weight:700;color:var(--ink);line-height:1.4}
/* 閉じるは色だけの × でなく、aria-label 付きの本物の button */
.x{flex:none;width:32px;height:32px;display:grid;place-items:center;border:0;border-radius:8px;
  background:rgba(255,255,255,.6);color:var(--ink);cursor:pointer}
.x:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
.modal .bd{padding:18px 22px 8px;font-size:13px;line-height:1.85;color:var(--body)}
.acts{display:flex;gap:10px;justify-content:flex-end;padding:14px 22px 20px}
.btn{font:inherit;font-size:13px;font-weight:700;border-radius:9px;padding:10px 16px;
  min-height:44px;cursor:pointer}
.btn.ghost{background:#fff;border:1.5px solid var(--line);color:var(--ink)}
.btn.danger{background:var(--danger);border:1.5px solid var(--danger);color:#fff}
.btn:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
</style></head><body>
<div class="page">
  <h3>アカウント設定</h3>
  <p>下のボタンでダイアログを開きます。開くとフォーカスがダイアログ内に移り、Tab は内側だけを巡回します。Esc キーか背景クリックで閉じ、フォーカスはボタンに戻ります。</p>
  <button class="open-btn" id="opener">アカウントを削除</button>
</div>

<div class="scrim" id="scrim" hidden>
  <div class="modal" role="dialog" aria-modal="true" aria-labelledby="mTitle" aria-describedby="mDesc">
    <div class="bar">
      <h2 id="mTitle">本当にアカウントを削除しますか?</h2>
      <button class="x" id="closeX" aria-label="閉じる">
        <svg width="15" height="15" viewBox="0 0 15 15" aria-hidden="true"><path stroke="currentColor" stroke-width="1.8" stroke-linecap="round" d="M3 3l9 9M12 3l-9 9"/></svg>
      </button>
    </div>
    <div class="bd" id="mDesc">この操作は取り消せません。作成したプロジェクトと設定はすべて失われ、復元できません。続けてよろしいですか?</div>
    <div class="acts">
      <button class="btn ghost" id="cancelBtn">キャンセル</button>
      <button class="btn danger" id="confirmBtn">削除する</button>
    </div>
  </div>
</div>

<script>
const scrim=document.getElementById('scrim'), opener=document.getElementById('opener');
const modal=scrim.querySelector('.modal');
let lastFocus=null;
function focusables(){return modal.querySelectorAll('button,[href],input,select,textarea,[tabindex]:not([tabindex="-1"])');}
function open(){
  lastFocus=document.activeElement;
  scrim.hidden=false;
  document.body.style.overflow='hidden';   // 開いている間は背後のスクロールを止める
  document.getElementById('cancelBtn').focus();
  document.addEventListener('keydown',onKey);
}
function close(){
  scrim.hidden=true;
  document.body.style.overflow='';          // スクロールロックを解除
  document.removeEventListener('keydown',onKey);
  if(lastFocus) lastFocus.focus();     // フォーカスをトリガに戻す
}
function onKey(e){
  if(e.key==='Escape'){ close(); return; }
  if(e.key==='Tab'){                    // フォーカストラップ
    const f=focusables(), first=f[0], last=f[f.length-1];
    if(e.shiftKey && document.activeElement===first){ e.preventDefault(); last.focus(); }
    else if(!e.shiftKey && document.activeElement===last){ e.preventDefault(); first.focus(); }
  }
}
opener.addEventListener('click',open);
document.getElementById('closeX').addEventListener('click',close);
document.getElementById('cancelBtn').addEventListener('click',close);
document.getElementById('confirmBtn').addEventListener('click',close);
scrim.addEventListener('click',e=>{ if(e.target===scrim) close(); }); // 背景クリックで閉じる
open(); // デモは開いた状態で表示
</script>
</body></html>
出典・参考: 自作の再現実装。ダイアログの role・フォーカス管理・閉じる手段は WCAG 4.1.2/2.4.3/2.1.1、タップ領域は 2.5.8 に基づく。コントラスト値は当方で実測。