UIの合否は、主観ではなく数値で出せます。タップ領域が24×24 CSSピクセル以上か、文字と背景のコントラスト比が4.5:1以上か、幅320ピクセルで横スクロールが発生しないか。この3つは、ブラウザのコンソールに数行貼るだけでページ全体を一括判定できます。
3つとも、合否ラインの出どころがW3CのWCAG 2.2にあります。つまり「なんとなく窮屈」ではなく「24ピクセルの円が交差している」と言える形で指摘でき、直ったかどうかも同じスクリプトで確定できます。この記事では、基準の原文・そのまま貼って動くスクリプト・落ちたときの直し方の3点を扱います。
この手順は、書き手のサイトにも容赦なく効きます。実際にこの3つを当サイト(CodeQuest.work)へ通したところ、WCAG 1.4.10(幅320ピクセルで16ピクセルの横溢れ)とWCAG 1.4.3(CTAリンクのコントラスト比3.51:1)の2件が不適合として出ました。どちらも2026年8月2日に修正済みで、記事内には検出時の数値と是正後の実測値を両方載せています。
UIの合否は3つの数値で決まる
デザインレビューが「なんとなく窮屈」「ちょっと読みにくい」で止まってしまうのは、指摘の側に再現できる根拠が無いからです。数値に置き換えると、この行き止まりが消えます。
「使いやすい」を数値に置き換える理由
数値で出せると、実務で3つのことが変わります。第一に、指摘が再現できます。「このアイコン、隣と近すぎませんか」ではなく「このアイコンの中心に置いた直径24ピクセルの円が、隣のボタンと交差しています」と言えます。第二に、直ったかどうかが確定します。同じスクリプトを再実行して0件になれば完了で、感想の応酬になりません。第三に、影響範囲が一度に分かります。ページ内の全要素を機械的に走査するので、見落としが減ります。
逆に、数値にできない領域もあります。導線が分かりやすいか、迷わず目的にたどり着けるかは、DOMの値からは出ません。この記事は「数値で出せる側」だけを引き受けます。
測る3項目と、根拠になるWCAG 2.2の達成基準
この記事で測るのは次の3項目です。いずれもW3CのWeb Content Accessibility Guidelines (WCAG) 2.2(2026年8月2日確認)に合否ラインの原文があります。
| 測る項目 | 合否ライン | 達成基準 | レベル |
|---|---|---|---|
| タップ領域 | 24×24 CSSピクセル以上 | SC 2.5.8 Target Size (Minimum) | AA |
| タップ領域(強化) | 44×44 CSSピクセル以上 | SC 2.5.5 Target Size (Enhanced) | AAA |
| 文字のコントラスト | 4.5:1以上(大きい文字は3:1) | SC 1.4.3 Contrast (Minimum) | AA |
| UI部品・図形のコントラスト | 3:1以上 | SC 1.4.11 Non-text Contrast | AA |
| 幅320ピクセルでの折り返し | 二方向スクロールが出ない | SC 1.4.10 Reflow | AA |
WCAG 2.2はWebコンテンツの適合基準なので、公開しているWebサイトの合否判定にはこれを使うのが筋です。アクセシビリティの全体像や、どのレベルを目標に置くかについてはWebアクセシビリティの基本とWCAG 2.2の考え方で扱っています。
UIの検査に合格しても、UXが良いとは限らない
先に線を引いておきます。この記事が測るのはUIの側だけで、それに合格することはUXが良いことを意味しません。「UIがうまく機能していればUXも良くなる」という説明を見かけますが、UXという語を定義した原典はこれを否定しています。
Don NormanとJakob NielsenによるThe Definition of User Experience (UX)(Nielsen Norman Group、1998年8月8日公開/2026年8月2日確認)は、UXを「企業・そのサービス・その製品に対する、エンドユーザーのあらゆる接点」と定義したうえで、映画レビューサイトの例を挙げています。作品を探すUIが完璧でも、データベースに大手スタジオ作品しか入っていなければ、独立系作品を探しに来たユーザーのUXは悪い、という例です。つまりUIの品質はUXの一部でしかありません。
そのため、この記事は「なぜその設計が良いのか」という理論には踏み込みません。近接・整列・反復といった原則の背景はUI/UXの質を上げるデザイン理論の基本原則に、キーボード操作やモーダルの設計といった実装側はアクセシブルなUXコンテンツの作り方にまとめてあります。
タップ領域の最小サイズは、基準ごとに食い違っている
「タップ領域は44×44ピクセル以上」という説明は日本語の解説記事でよく見かけますが、原文に当たると、この数値は基準ごとに違います。どれを採用するかを決めないと、検査した結果の意味も決まりません。
WCAG 2.2:AAは24×24 CSSピクセル、44×44はAAA
WCAG 2.2の SC 2.5.8 Target Size (Minimum) の原文は「The size of the target for pointer inputs is at least 24 by 24 CSS pixels」で、レベルはAAです。44×44 CSSピクセルを求めているのは SC 2.5.5 Target Size (Enhanced) で、こちらはレベルAAAの強化基準です。つまり44×44は「必須」ではなく「最高レベルの目標値」にあたります。
さらに SC 2.5.8 には Spacing・Equivalent・Inline・User Agent Control・Essential の5つの例外があります。このうち実務で効く2つは後述しますが、例外を無視して「24ピクセル未満は全部NG」と数えると、実際には適合しているページが大量に落ちます。
Apple HIG:44×44ptは「既定値」であって「最小」ではない
ここは日本語の解説と現行原文がはっきり食い違う箇所です。AppleのHuman Interface Guidelines — Accessibility(2026年8月2日確認)には、プラットフォーム別のコントロールサイズが表で示されています。
| プラットフォーム | 既定のコントロールサイズ | 最小のコントロールサイズ |
|---|---|---|
| iOS, iPadOS | 44×44 pt | 28×28 pt |
| macOS | 28×28 pt | 20×20 pt |
| tvOS | 66×66 pt | 56×56 pt |
| visionOS | 60×60 pt | 28×28 pt |
| watchOS | 44×44 pt | 28×28 pt |
iOS・iPadOSの列は「既定44×44 pt/最小28×28 pt」です。「Appleの最小タップ領域は44×44pt」という定番の説明は、現行のHIGの表記とは一致しません。44×44 ptはDefault control size、つまり既定値の側に書かれています。
同じページには間隔についての記述もあり、ベゼルのある要素なら周囲におよそ12ポイント、ベゼルの無い要素なら見える縁の周囲におよそ24ポイントの余白を取るとよい、とされています。サイズだけでなく間隔も同じくらい重要だ、という位置づけです。
Material Design 3:タッチ48×48dp、ポインタ44×44dp
GoogleのMaterial Design 3 — Designing: structure(2026年8月2日確認)は、次のように書いています。
- タッチターゲットは多くのプラットフォームで48×48dp以上を検討する。このサイズは画面サイズによらず物理サイズでおよそ9mmになる
- タッチスクリーン要素に推奨される物理サイズは7〜10mm
- アイコンの見た目が24×24dpでも、周囲のパディングを含めて48×48dpのタッチターゲットを構成する
- マウスやスタイラス向けのポインタターゲットは44×44dp以上を検討する
- ターゲット同士は8dp以上あけると、情報密度と使いやすさのバランスが取れる
注目したいのは、このページに「Note: iOS recommends 44 x 44dp targets.」という注記が残っている点です。前述のとおり現行のApple HIGは44×44 ptを既定値として書いているので、Material Design側の注記のほうが古い理解に基づいています。基準同士は互いに追随していない、と考えたほうが安全です。
公開Webサイトではどの数値を採用するか
Webサイトなら、次の2本立てで置くのが実務的です。
- 落ちてはいけない線=24×24 CSSピクセル(WCAG 2.2 レベルAA)。適合基準として参照されるのはWCAGであり、AAは公共調達や法令の文脈で引かれる水準だからです
- 設計目標=44〜48 CSSピクセル。主要OSがコントロールの既定値として採用しているサイズで、指で押す操作の実感に合っています
この2本立てにしておくと、検査の役割がはっきりします。スクリプトが判定するのは1の「落ちてはいけない線」で、2は設計時に自分たちで守る目標値です。設計側の数値を先に決めておく手順はデザインカンプ着手前に決めるWebデザインのルールで扱っています。
実測①:タップ領域をコンソールで一括判定する
ここから実測に入ります。3本ともブラウザのコンソールに貼るだけで動きます。コンソールの開き方やパネルの基本操作はChrome DevToolsで仮説と検証を回す手順にまとめてあるので、そちらを先に読んでも構いません。
「24ピクセル未満は即NG」ではない:先に例外を実装する
SC 2.5.8の5つの例外のうち、実務でほぼ必ず効くのは次の2つです。
- Spacing:24×24 CSSピクセル未満のターゲットでも、それぞれの外接矩形の中心に直径24 CSSピクセルの円を置いたとき、その円が他のターゲットや他の未達ターゲットの円と交差しなければ適合する
- Inline:ターゲットが文の中にあるか、あるいはターゲットでないテキストの行の高さによってサイズが制約されている場合は対象外
SC 2.5.5(AAA)のInline例外は「in a sentence or block of text」ですが、SC 2.5.8(AA)の原文は「in a sentence or its size is otherwise constrained by the line-height of non-target text」で、条件の書き方が違います。AAを判定するなら後者に合わせます。詳しい解説はW3CのUnderstanding SC 2.5.8 Target Size (Minimum)(2026年8月2日確認)にあります。
この2つを実装していないチェッカーは偽陽性を大量に出します。どのくらい差が出るかは後述の実測値で確認できます。
コンソールに貼るスクリプト
判定したいページを開き、コンソールに次のコードを貼り付けて実行します。
const SEL = 'a[href],button,input:not([type=hidden]),select,textarea,summary,[role="button"]';
// Inline例外: 文中にあり行の高さに縛られるリンクは対象外
const isInline = el => el.tagName === 'A' && getComputedStyle(el).display === 'inline'
&& [...(el.parentElement ? el.parentElement.childNodes : [])].some(n => n.nodeType === 3 && n.textContent.trim());
const targets = [];
for (const el of document.querySelectorAll(SEL)) {
const cs = getComputedStyle(el);
if (cs.visibility === 'hidden' || cs.display === 'none') continue;
const rects = el.getClientRects(); // 折り返したリンクを行ごとに扱う
if (!rects.length) continue;
const r = rects[0];
if (!r.width || !r.height) continue;
targets.push({ r, inline: isInline(el),
label: (el.innerText || el.getAttribute('aria-label') || el.tagName).trim().slice(0, 24) });
}
const under = targets.filter(t => (t.r.width < 24 || t.r.height < 24) && !t.inline);
const fails = [];
for (const t of under) {
const cx = t.r.left + t.r.width / 2, cy = t.r.top + t.r.height / 2;
for (const o of targets) {
if (o === t) continue;
const oUnder = (o.r.width < 24 || o.r.height < 24) && !o.inline;
let d;
if (oUnder) {
d = Math.hypot(cx - (o.r.left + o.r.width / 2), cy - (o.r.top + o.r.height / 2));
if (d >= 24) continue;
} else {
d = Math.hypot(Math.max(o.r.left - cx, 0, cx - o.r.right), Math.max(o.r.top - cy, 0, cy - o.r.bottom));
if (d >= 12) continue;
}
fails.push({ label: t.label, w: Math.round(t.r.width), h: Math.round(t.r.height),
other: o.label, dist: +d.toFixed(1) });
break;
}
}
console.table(fails);
console.log({ targets: targets.length, undersized: under.length, spacingFail: fails.length });実装で1点だけ注意があります。矩形を取るのに getBoundingClientRect() ではなく getClientRects() を使っている点です。文中のリンクが2行に折り返すと矩形は2つになりますが、getBoundingClientRect() は行をまたいだ結合矩形を返すため、実際の当たり判定より大きく見えてしまいます。行ごとの矩形が欲しいので getClientRects() の先頭を使います。
出力の読み方と合否ライン
最後の行に出るのが判定結果です。合否ラインは spacingFail が0かどうかで、0ならSC 2.5.8に適合、1以上なら不適合です。undersized は「24ピクセル未満だがInline例外にも当たらない」ターゲットの数で、これが多くても spacingFail が0なら問題ありません。
このページ自身に対して実行した実測値です(2026年8月2日、Chromium)。列の見出しはウィンドウ幅ではなく clientWidth の値です。例外を実装するかどうかで結果がどれだけ変わるかが分かります。
| 判定の段階 | clientWidth 1280 | clientWidth 320 |
|---|---|---|
| 検出したターゲット総数 | 95 | 106 |
| 素朴に「24ピクセル未満」で数える | 32件 | 27件 |
| Inline例外を適用(undersized) | 24件 | 23件 |
| Spacing例外を適用(spacingFail) | 0件 | 0件 |
素朴に数えると32件が引っかかりますが、例外を適用すると0件になります。この差が、素朴なチェッカーの出力を信用してはいけない理由です。32件のリストを渡されて全部直そうとすると、直す必要のないものを大量に触ることになります。
NGだったときの直し方
spacingFail が1以上なら、出力された表の label(要素のテキスト)、w / h(実寸)、other(近すぎる相手)、dist(距離)で原因を特定できます。分岐は4つです。
- 幅か高さのどちらかだけ足りない:
paddingを足す。width/heightを直接増やすとレイアウトが動きます - 隣接要素と近すぎる(
distが小さい):marginかgapを広げ、中心間距離を24ピクセル以上にする - アイコンだけ小さい:見た目のサイズは変えず、擬似要素や
paddingで当たり判定だけ広げる。Material Designの「アイコン24dp/タッチターゲット48dp」と同じ考え方です - 文中のリンク:Inline例外に当たるので直しません。行の高さに縛られるリンクを一律に大きくすると、かえって本文が読みにくくなります
実測②:コントラスト比を全テキストで測る
コントラスト比は、色を1つずつツールに入力していては終わりません。ページ内のテキスト要素をすべて走査して、必要値を下回るものだけを出します。
相対輝度の式と、コピペで出回っている古い定数
WCAG 2.2の relative luminance の定義は、sRGBの各チャンネルを次の式で線形化してから L = 0.2126R + 0.7152G + 0.0722B で合成する、というものです。しきい値以下なら12.92で割り、超えるなら ((v+0.055)/1.055) の2.4乗を取ります。コントラスト比は明るいほうの相対輝度を L1、暗いほうを L2 として (L1+0.05)/(L2+0.05) です。
問題はこのしきい値です。WCAG 2.2の現行値は 0.04045 ですが、ネット上に出回っているスニペットは今も 0.03928 のものが多くあります。原文のNote 2に「2021年5月より前は 0.03928 だった。古い仕様から取られていたので更新した。このガイドラインの文脈では計算結果に実務上の影響はない」と明記されているので、既存のコードを慌てて直す必要はありません。ただし新しく書くなら 0.04045 に合わせます。
「大きい文字」の3:1に、日本語サイトが逃げてはいけない理由
SC 1.4.3は通常のテキストに4.5:1を求めますが、「大きい文字」なら3:1に緩和されます。W3CのUnderstanding SC 1.4.3 Contrast (Minimum)(2026年8月2日確認)には「ポイントとCSSピクセルの比は 1pt = 1.333px であり、したがって14ptと18ptはおよそ 18.5px と 24px にあたる」と書かれています。太字なら14pt相当、通常なら18pt相当が境界です。
日本語の解説では 18.66px という数値もよく見ますが、これは 14×4/3 を厳密に計算した値で、W3Cが書いている 18.5px とは別の丸めです。境界付近のフォントサイズは判定が割れうるので、機械判定に任せず手で確認してください。
さらに重要なのは、WCAG 2.2の用語定義側の記述です。large scale (text) の定義は「with at least 18 point or 14 point bold or font size that would yield equivalent size for Chinese, Japanese and Korean (CJK) fonts」となっており、CJKフォントについては「同等のサイズになるフォントサイズ」という別条件が置かれています。日本語のフォントは同じpt値でも字面が小さく見えるため、ptだけで機械的に「大きい文字だから3:1でよい」と判定するのは危険です。
実務的な結論は明快です。日本語サイトでは「大きい文字」の3:1に逃げず、4.5:1で通すのを既定にします。掲載するスクリプトも、先頭のフラグでこの挙動を切り替えられるようにしてあります。
なお、4.5:1という数値には根拠があります。同じUnderstandingには、標準的な視力に対する最低限のコントラストとして3:1が採用されており、視力20/40の人はコントラスト感度がおよそ1.5倍落ちるという知見から 3 × 1.5 = 4.5:1 が導かれた、と説明されています。
コンソールに貼るスクリプト
先頭の CJK_STRICT を true にすると、大きい文字でも4.5:1を要求します。英語圏向けのページなど、原文どおりの緩和を使いたい場合は false にしてください。
const CJK_STRICT = true; // 日本語ページでは「大きい文字」の3:1緩和を使わない
const parse = c => { const m = c.match(/rgba?\(([^)]+)\)/); if (!m) return null;
const p = m[1].split(/[,\s\/]+/).filter(Boolean).map(Number);
return { r: p[0], g: p[1], b: p[2], a: p.length > 3 ? p[3] : 1 }; };
const lum = ({ r, g, b }) => { const f = v => { v /= 255;
return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); };
return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b); };
const ratio = (a, b) => { const L1 = lum(a), L2 = lum(b);
return (Math.max(L1, L2) + 0.05) / (Math.min(L1, L2) + 0.05); };
const effBg = el => { let n = el;
while (n) { const cs = getComputedStyle(n);
if (cs.backgroundImage !== 'none') return null; // 背景画像・グラデーションは自動判定しない
const c = parse(cs.backgroundColor);
if (c && c.a === 1) return c;
n = n.parentElement; }
return { r: 255, g: 255, b: 255, a: 1 }; };
const fail = [], unknown = [];
for (const el of document.querySelectorAll('p,li,a,h1,h2,h3,h4,span,td,th,button,label')) {
if (![...el.childNodes].some(n => n.nodeType === 3 && n.textContent.trim())) continue;
const cs = getComputedStyle(el);
if (cs.visibility === 'hidden' || cs.display === 'none') continue;
const r = el.getBoundingClientRect();
if (!r.width || !r.height) continue;
const fg = parse(cs.color), bg = effBg(el);
const txt = el.innerText.trim().slice(0, 26);
if (!bg) { unknown.push({ txt, fg: cs.color }); continue; }
const fs = parseFloat(cs.fontSize), wt = parseInt(cs.fontWeight) || 400;
const need = (!CJK_STRICT && (fs >= 24 || (fs >= 18.5 && wt >= 700))) ? 3 : 4.5;
const cr = ratio(fg, bg);
if (cr < need) fail.push({ txt, fs, wt, cr: +cr.toFixed(2), need,
fg: cs.color, bg: `rgb(${bg.r}, ${bg.g}, ${bg.b})` });
}
console.table(fail);
console.table(unknown);
console.log({ failCount: fail.length, needsManualCheck: unknown.length });1つ目の表が不適合、2つ目の表が「自動判定できなかったので目視が必要」な要素です。合否ラインは failCount が0、かつ needsManualCheck の要素を目視で確認済みであることです。needsManualCheck を放置したまま「0件でした」と報告すると、確認していない領域を見逃します。
背景が画像なら自動判定できない:ガードを外すと偽陽性が出る
このスクリプトの肝は effBg() の中の1行、backgroundImage !== 'none' なら null を返してしまう部分です。祖先をたどって背景色を探す途中で背景画像やグラデーションに当たったら、その要素の背景色は決まらないので判定をやめるという意味です。
このガードがどれだけ効くかを、このページで実測しました(2026年8月2日、clientWidth 1280)。
- ガードあり:
failCount: 0/needsManualCheck: 9。背景がグラデーションの9要素が目視送りに分離される - ガードなし:
failCount: 8。分離されなかった9要素のうち8件が、背景色を白と誤認した結果 1.06:1 などの極端な値で不適合と判定される
ガードなしで出た8件はすべて、グラデーション背景の上に置かれた白文字でした。実際には読めているのに不適合として並ぶので、この状態のレポートを渡すと受け取った側がチェッカーそのものを信用しなくなります。自動判定の価値は「出た件数」ではなく「出た件数がすべて本物であること」にあります。
同じ理由で、半透明を重ねた配色、mix-blend-mode、テキストシャドウ、写真の上に載せた文字も自動判定の対象外です。これらは背景画像の一番明るい部分と一番暗い部分の両方を取り出して、手で計算します。
実測:当サイトのCTAリンクが3.51:1で落ちたので直した
このスクリプトをこのページに対して実行したときに、実際に1件出ました。当サイトのプロモーション枠にあるCTAリンクです。
- 文字色
#0097A7(rgb(0, 151, 167))、背景#FFFFFF、16px / font-weight 700 - コントラスト比 3.509:1(必要値 4.5:1)
迷いどころは「太字だから3:1でよいのでは」という点ですが、太字で3:1に緩和されるのは14pt相当、つまり18.5px以上の場合です。16pxの太字は緩和の対象外なので、必要値は4.5:1のまま。前述のCJK注記もあるので、日本語ページで3:1側に寄せる判断は取りません。したがって不適合です。
直し方の選択肢は3つあります。(a) 文字色を暗くする、(b) 文字の背後に背景色を敷く、(c) フォントサイズを18.5px以上に上げて3:1の緩和に乗せる。ただし(c)は日本語ページでは推奨しません。既定は(a)です。文字色だけを変えるのは影響範囲が最も狭く、レイアウトが動かないためです。
実際に #0097A7 を #00757F に変更して本番へ反映し、同じスクリプトで再測定しました(2026年8月2日)。
| 時点 | getComputedStyleのcolor | コントラスト比 | failCount |
|---|---|---|---|
| 是正前 | rgb(0, 151, 167) | 3.509:1 | 1 |
| 是正後 | rgb(0, 117, 127) | 5.453:1 | 0 |
是正後の getComputedStyle は color: rgb(0, 117, 127) / 背景 rgb(255, 255, 255) / font-size: 16px / font-weight: 700 で、フォントサイズと太さは変えていません。色だけを1段暗くすれば通る、というのがこの種の不適合のほとんどです。
実測③:幅320ピクセルで横に溢れないか
3つ目は横溢れです。レイアウトが壊れていることが最も分かりやすく出る指標で、直す優先度も一番高い項目になります。ブレイクポイントの考え方そのものはレスポンシブデザインの基本原則で扱っています。
WCAG 1.4.10 Reflow が定める320 CSSピクセル
SC 1.4.10 Reflow の原文は、情報や機能を失わず、かつ二方向のスクロールを必要とせずにコンテンツを提示できること、と述べたうえで、垂直スクロールするコンテンツについては幅320 CSSピクセル相当、水平スクロールするコンテンツについては高さ256 CSSピクセル相当、という基準を置いています。レベルはAAです。
なぜ320なのかもNote 1に書かれています。320 CSSピクセルは、幅1280 CSSピクセルのビューポートを400%にズームした状態と等価だからです。小さなスマートフォンのためだけの基準ではなく、拡大表示のための基準でもあります。
ただし「使用や意味のうえで二次元レイアウトを必要とする部分」は例外です。Note 2には例として、理解に必要な画像(地図や図など)、動画、ゲーム、プレゼンテーション、データテーブル(個々のセルではなく表そのもの)、そしてコンテンツを操作しながらツールバーを表示し続ける必要のあるインターフェースが挙げられています。表そのものは例外に当たりますが、表がページ全体を横に押し広げてよいという意味ではありません。表は横スクロールできるラッパーの内側に収め、ページの本体は320で収まるようにします。
ウィンドウ幅320pxでは測れない:見るのは clientWidth
ここが一番間違えやすい箇所です。デスクトップのブラウザでは、スクロールバーが表示幅を食います。ウィンドウを320pxにしても document.documentElement.clientWidth は305pxといった値になり、幅320pxの要素が「溢れている」と誤判定されます。逆にスクロールバーが重なって表示される環境では食われません。つまりウィンドウ幅は環境依存で、合否の基準にできません。
合わせるのは clientWidth です。スクリプトが出力する clientWidth を見ながらウィンドウ幅を調整し、320になった状態で測ります。デバイスツールバーの表示が「320」になっているかどうかは根拠にしません。見た目ではなく、DOMから読んだ数値で判断します。
コンソールに貼るスクリプト
溢れ幅と、溢れている要素を右端の遠い順に並べます。
const de = document.documentElement;
const over = [...document.querySelectorAll('*')]
.map(e => ({ e, r: e.getBoundingClientRect() }))
.filter(x => x.r.width > 0 && x.r.right > de.clientWidth + 0.5)
.sort((a, b) => b.r.right - a.r.right)
.map(x => ({ tag: x.e.tagName.toLowerCase(), cls: (x.e.getAttribute('class') || '').slice(0, 40),
w: Math.round(x.r.width), right: Math.round(x.r.right) }));
console.log({ clientWidth: de.clientWidth, scrollWidth: de.scrollWidth,
overflowPx: de.scrollWidth - de.clientWidth, overflowCount: over.length });
console.table(over.slice(0, 10));合否ラインは、clientWidth が320の状態で overflowPx が0、かつ overflowCount が0であることです。overflowPx が1以上なら不適合で、表の先頭に出た要素(右端が最も遠い要素)が原因の本体です。
実測:当サイトの記事ページが16ピクセル溢れていたので直した
このスクリプトで当サイトを測ったところ、記事ページで不適合が出ました。clientWidth を320にした状態で、scrollWidth が336。差の16ピクセルぶん、右に溢れていました。同じ条件でもトップページでは溢れが出ませんでした。横溢れはテンプレート単位で出るので、代表ページを1つ測って終わりにしないことです。
出力の先頭にいたのは広告の ins 要素で、矩形は left: 16 / width: 320 / right: 336。幅320px固定の埋め込みが、左右に16pxのpaddingを持つコンテナの内側に入っていたという構図です。16 + 320 = 336 で、ぴったり計算が合います。
この型のやっかいなところは、ページ内の要素を片端から max-width: 100% にしても直らない点です。埋め込みの幅は配信側のCSSで固定されているので、ページ側の指定では縮みません。当サイトの場合も、広告の実寸320×100は規定サイズなので変えられません。
そこで、コンテンツ幅が320pxを下回るビューポートのときだけ、埋め込みを包むラッパーだけをpaddingの外へ逃がす方法を取りました。左右に負のマージンを当てて、そのブロックだけを画面全幅に広げる書き方です。
@media (max-width: 351px) {
.embed-fullbleed {
margin-left: calc(50% - 50vw);
margin-right: calc(50% - 50vw);
}
}ブレイクポイントの351pxは計算で出しています。左右16pxのpaddingを持つコンテナなら、コンテンツ幅が320pxちょうどになるビューポート幅は 320 + 16 + 16 = 352px。それを下回るときだけ逃がせばよいので上限は351pxです。自分のサイトのpaddingの値に置き換えて計算してください。calc(50% - 50vw) は「そのブロックだけを画面全幅に広げる」定番の書き方で、埋め込み自体のサイズには一切触れません。
是正後に同じスクリプトで再測定した結果です(2026年8月2日)。
| clientWidth | 是正前 scrollWidth | 是正後 scrollWidth | 是正後 overflowCount |
|---|---|---|---|
| 320 | 336(16px溢れ) | 320 | 0 |
| 360 | 360(溢れなし) | 360 | 0 |
| 390 | 390(溢れなし) | 390 | 0 |
原因の裏取りもしています。是正後のページで、逃がすために当てた負のマージンをコンソールから 0px に戻すと、ins の矩形が left: 16 / width: 320 / right: 336 に戻り、scrollWidth も336へ戻りました。「そこが原因だった」を、直す前の推測ではなく、直したあとに元へ戻して確認すると、別の原因を取り違えていた可能性を潰せます。
NGだったときの直し方
溢れの原因はほぼ4パターンに収まります。
- 幅を自分で変えられる要素(画像・動画・図):
max-width: 100%を当てる - 幅を変えられない埋め込み(広告・外部iframe):包むラッパーを
overflow-x: autoにするか、上のように全幅へ逃がす - 長い英数字・URL・コード:
overflow-wrap: anywhereを当てる。日本語は自然に折り返すので、この型は英数字で起きます - 表:横スクロールできるラッパーで包む。表自体はReflowの例外ですが、ページ全体を横に伸ばしてはいけません
測ったあとにやること
3本の検査を通すと、直すべき箇所のリストが手に入ります。ここからは、直す順番と再発防止の話です。
直す順番
影響の大きい順に片付けます。
- 320ピクセル幅の横溢れ。レイアウト全体が壊れ、他の2つの測定値も歪みます。ここを先に直さないと再測定のたびに数字が動きます
- コントラスト不足。読めない文字は機能していないのと同じです。色の変更だけで済むことが多く、費用対効果も高いです
- タップ領域のSpacing違反。誤タップの原因ですが、直せる箇所が限られるうえ、周囲の余白を動かすと他のレイアウトに波及します
再発を止める:数値を1か所に集める
検査で出てくる問題の多くは「その場で決めた数値」が原因です。ボタンの高さを個別に書いた、リンク色をコンポーネントごとに指定した、といった小さな逸脱が積み上がります。
最小タップ領域・本文色・リンク色・最小フォントサイズは、CSSカスタムプロパティに寄せて1か所で管理します。1か所に集まっていれば、次に不適合が出たときの修正も1行で済みます。コントラストを設計段階で担保する考え方はWebデザインの配色設計とカラーパレットの作り方にまとめてあります。
自動化する前に、例外の扱いを決める
この3本をCIに組み込みたくなりますが、その前に決めておくことがあります。Inline例外・Spacing例外・背景画像の上のテキストを、それぞれどう扱うかです。
決めずに自動化すると、記事で見たとおりの差が出ます。タップ領域は素朴判定32件に対して例外適用後0件、コントラストはガードなし8件に対してガードあり0件。本物が0件なのに毎回40件の警告が出るチェックは、数回で誰も見なくなります。まず例外を実装し、手元で0件を確認してから自動化に載せるのが順番です。
この3つでは測れないもの
DOMの数値で判定できない領域は残ります。キーボードだけで全機能に到達できるか(SC 2.1.1)、フォーカスされた要素が固定ヘッダーなどに隠れないか(SC 2.4.11)、スクリーンリーダーで意味の通る順に読み上げられるか。これらは実際に操作して確かめるしかありません。
ユーザー自身に操作してもらう検証も、この3本の外側にあります。Jakob NielsenのWhy You Only Need to Test with 5 Users(Nielsen Norman Group、2000年3月18日公開/2026年8月2日確認)は、1人のテストで見つかる問題の割合を L、参加者数を n として、発見率を 1-(1-L)^n でモデル化し、多数のプロジェクトを平均した L の代表値を31%としています。この値を入れると5人で約85%です。ただし2000年に示された推定モデルであり、対象タスクが均質な場合の話である点は押さえておいてください。
数値で出せる部分を機械に任せ、人手のリソースを「数値にならない部分」へ回す。それがこの3本を用意する目的です。
検査で不適合が出たものの、既存のCSS構造に手を入れづらくて直しきれないという場合は、実装ごと相談する方法もあります。
よくある質問(FAQ)
Q. UIの使いやすさは数値で判定できますか?
一部は判定できます。タップ領域の実寸、文字と背景のコントラスト比、幅320 CSSピクセルでの横溢れの3つは、WCAG 2.2に合否ラインの原文があり、ブラウザのDOM APIで機械的に測れます。一方で、導線が分かりやすいか・迷わずに目的へ到達できるかは数値では出ないため、実際のユーザーに操作してもらう検証が別に必要になります。
Q. タップ領域は44×44ピクセルにしないといけませんか?
いいえ。WCAG 2.2で必須(レベルAA)なのは24×24 CSSピクセルで、44×44 CSSピクセルはレベルAAAの強化基準です。さらに24ピクセル未満でも、外接矩形の中心に置いた直径24ピクセルの円が他のターゲットと交差しなければ適合します(Spacing例外)。公開Webサイトなら「落ちてはいけない線は24ピクセル、設計目標は44〜48ピクセル」と2本立てで置くのが現実的です。
Q. AppleとGoogleとWCAGで最小サイズが違うのはなぜですか?
目的が違うためです。WCAGはWebコンテンツのアクセシビリティ適合基準、Apple HIGとMaterial Designは各OSのネイティブUI設計指針で、それぞれ独立に決められています。現行のApple HIGはiOSのコントロールについて既定44×44pt・最小28×28ptと記載し、Material Design 3はタッチ48×48dp・ポインタ44×44dpを推奨しています。Webサイトの合否判定にはWCAGを使うのが筋です。
Q. コントラスト比が基準を下回ったら必ず直すべきですか?
通常のテキストとUI部品は直したほうがよいです。ただしSC 1.4.3の原文にはIncidentalとLogotypesという除外が置かれています。Incidentalは、無効状態のUIコンポーネントの一部であるテキスト、純粋な装飾、誰にも見えないテキスト、他の視覚情報を多く含む絵の一部であるテキストで、いずれもコントラストの要件がありません。Logotypesはロゴやブランド名の一部であるテキストで、こちらも要件がありません。判定前に、その要素がこれらに当たらないかを確認してください。
Q. 文中のリンクが24ピクセル未満でも問題にならないのはなぜですか?
SC 2.5.8にInline例外があるためです。原文は「ターゲットが文の中にあるか、ターゲットでないテキストの行の高さによってサイズが制約されている場合」という書き方で、行の高さに縛られる文中リンクを一律に大きくすると本文が読みにくくなることを踏まえた例外です。ナビゲーションやボタンなど、文章の外にあるターゲットはこの例外には当たりません。
Q. コンソールで出た結果はそのまま信じてよいですか?
いいえ。背景が画像やグラデーションの要素はDOMから背景色を確定できないため、自動判定できません。掲載したスクリプトは、そうした要素を不適合ではなく「要目視」として分離します。実測では、このガードを外すとグラデーション背景の白文字8件が不適合として誤検出されました。半透明の重ね、mix-blend-mode、写真の上の文字も同様に自動判定の対象外です。
Q. 幅320ピクセルで確認するのはなぜですか?
SC 1.4.10 Reflow が基準幅として320 CSSピクセルを定めているためです。原文のNote 1には、これは幅1280 CSSピクセルのビューポートを400%にズームした状態と等価だと注記されています。なおデスクトップのブラウザではスクロールバーが表示幅を食うため、ウィンドウ幅ではなく document.documentElement.clientWidth が320になった状態で測る必要があります。
Q. 大きい文字の3:1に緩和してもよいのはどんなときですか?
WCAG 2.2の原文では18pt以上、または14pt以上の太字が「大きい文字」にあたり、CSSピクセルに換算するとおよそ24pxおよび18.5pxです。ただし用語定義には「CJKフォントで同等のサイズになるフォントサイズ」という別条件が併記されており、日本語は同じpt値でも字面が小さく見えます。したがって日本語ページでは3:1に逃げず、4.5:1で通すのを既定にするのが安全です。
