CSSで、キーボード操作のときのフォーカス枠を見やすくする方法
キーボードで操作する人は、フォーカスの枠を見て、いまどこを選んでいるかを知ります。:focus-visible に outline と outline-offset を書くと、Tab キーで選んだときだけ太くはっきりした枠が出て、マウスでボタンを押したときには出ません。暗い背景にも置く部品なら、濃い色と白の2色の枠にすると、どちらの背景でも見えます。
検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。HTML と CSS のページを Playwright で開き、Playwright からキーボードの Tab キーとマウスのクリックを送ってフォーカスを動かし、getComputedStyle() の値とスクリーンショットの画素を記録しました。強制カラーモードは Playwright の emulateMedia({ forcedColors: 'active' }) で再現したもので、Windows のハイコントラスト設定の実機ではありません。Firefox と Safari、スマートフォンの実機は試していません。

:focus-visible に outline を書く
枠は次の CSS で出します。太さ 3px の線を、ボタンの縁から 2px 離して描きます。
.btn:focus-visible {
outline: 3px solid #067a98;
outline-offset: 2px;
}
検証環境でページを開き、Tab キーを押してボタンを順に選ぶと、どのボタンでも :focus と :focus-visible の両方が当てはまり、枠が出ました。マウスでクリックした場合は、フォーカスはボタンに移って :focus は当てはまるものの、:focus-visible は当てはまらず、枠は出ませんでした。上の GIF の最後の場面がこの状態です。
:focus に同じ枠を書いたボタンと比べると、違いはクリックしたときだけでした。
| 書き方 | Tab キーで選ぶ | マウスでクリック |
|---|---|---|
.btn-focus:focus に枠 |
枠が出る | 枠が出る |
.btn:focus-visible に枠 |
枠が出る | 枠が出ない |
テキストの入力欄に :focus-visible で枠 |
枠が出る | 枠が出る |
テキストの入力欄だけは、クリックしても :focus-visible が当てはまりました。MDN の :focus-visible のページにも、ボタンをクリックしたときは一般にフォーカスを表示しないが、文字を入力する欄では表示する、とあります。:focus-visible が当てはまるかどうかはブラウザが決めるので、CSS で決められるのは、当てはまったときの見た目だけです。
何も書いていないボタンでも、Tab キーで選ぶと Chromium の既定の枠が出ました。そのときの値は outline: auto 1px rgb(16, 16, 16) で、細い枠です。上の CSS は、この既定の枠を、太くて色のはっきりした枠に置き換えています。
MDN のブラウザー互換性データ(@mdn/browser-compat-data 8.1.8)によると、:focus-visible は Chrome と Edge が 86、Firefox が 85、Safari が 15.4 から使えます。
枠は border ではなく outline で描く
フォーカスしたときに border を太くして枠を出すと、ボタンが大きくなり、まわりの要素が動きます。検証環境で、ふだんは 1px の border を、フォーカスしたときだけ 3px にしたボタンを Tab キーで選ぶと、ボタンは 89×46px から 93×50px に大きくなり、右どなりのボタンが 4px 右へずれました。outline で枠を出したボタンは 91×46px のままで、右どなりのボタンも動きませんでした。
CSS の仕様(CSS UI 4)で outline を定めた節にも、outline は場所を取らない(まわりの配置に影響しない)と書かれています。同じ節には、outline は border-radius の丸みに沿って描くとあり、検証環境でも、角を丸めたボタンの枠は丸い角に沿っていました。
outline-offset は、要素の縁と枠のあいだのすき間を指定するプロパティです。この例では 2px あけて、ボタンの灰色の縁と枠を離しています。WCAG のフォーカスの外観の解説でも、枠を部品から少し離すと、枠が見やすくなることがあると書かれています。
暗い背景でも見えるよう、2色の枠にする
1色の枠は、背景の色によって見えにくくなります。濃い水色(#067a98)の枠を、白い背景と暗い背景(#1f2a30)に置いて比べました。
WCAG 2.2 の達成基準 2.4.13(フォーカスの外観、レベル AAA)は、フォーカスの枠に2つのことを求めています。1つは、フォーカスしていない部品を太さ 2px の線で囲んだときの面積以上の大きさがあること。もう1つは、その部分の画素が、フォーカスしていないときの同じ画素と比べて 3:1 以上のコントラスト比で変わることです。
そこで、フォーカスする前と後のスクリーンショットで、ボタンの左の外側(縦の中央)の同じ画素の色を取り、WCAG の相対輝度とコントラスト比の式で計算しました。
| 枠 | 白い背景でのコントラスト比 | 暗い背景でのコントラスト比 |
|---|---|---|
1色(#067a98) |
4.95 | 2.96 |
2色(#1f2a30 と白) |
14.66 | 14.66 |
1色の枠は、暗い背景では 2.96 で、3:1 に届きませんでした。2色の枠は次のように書きます。
.btn-2tone:focus-visible {
outline: 3px solid #1f2a30;
outline-offset: 0;
box-shadow: 0 0 0 6px #fff;
}
内側に濃い色の outline を 3px 描き、その外側に、白い box-shadow を出します。box-shadow はボタンの縁から 6px 広がります。検証環境の Chromium では、内側の 3px に outline が重なって描かれ、ボタンの縁から外へ 1〜3px が濃い色、4〜6px が白でした。白い背景では濃い色の線が、暗い背景では白い線が、それぞれ 3px の太さで見えます。

box-shadow で描くときは、透明な outline を残す
強制カラーモードは、Windows のハイコントラスト設定のように、ページの色を利用者が選んだ色に置き換える表示です。outline: none にして box-shadow だけで枠を描く書き方では、強制カラーモードで枠が消えました。
CSS の仕様で強制カラーモードの影響を定めた節には、強制カラーモードのとき box-shadow は none になり、outline-color などの色はシステムカラーに置き換わる、と書かれています。そこで、box-shadow で描くときも、透明な outline を残しておきます。
.btn-shadow-safe:focus-visible {
outline: 3px solid transparent;
box-shadow: 0 0 0 3px #067a98;
}
ふだんの表示では、透明な outline は見えず、box-shadow の枠だけが見えます。強制カラーモードを再現して Tab キーでボタンを選ぶと、box-shadow は none になりました。outline: none のボタンは、ボタンのまわり 6px の範囲で色の変わった画素が 0 個で、フォーカスしても見た目が変わりませんでした。透明な outline を残したボタンは、outline の色が rgba(0, 0, 0, 0)(透明)から Chromium の決めたシステムカラーに置き換わり、枠が見えました。

上の2色の枠も、内側の線は outline で描いています。強制カラーモードを再現して Tab キーで選ぶと、白い box-shadow は none になり、outline の線はシステムカラーで残りました。
overflow: hidden の箱の中では、枠を内側に描く
overflow: hidden の箱の中で、箱の端いっぱいに広がったリンクに外側の枠を描くと、箱からはみ出た部分が切れます。角を丸めたメニューの1行目のリンクに、outline-offset: 2px の枠を出して画素を数えると、枠の色の画素は上の辺と左右の辺で 0 個で、残っていたのは下の辺だけでした。
MDN の outline-offset のページにあるとおり、outline-offset に負の値を書くと、枠は要素の内側に描かれます。
.menu {
overflow: hidden;
}
.menu a:focus-visible {
outline: 3px solid #067a98;
outline-offset: -3px;
}
outline-offset: -3px にすると、太さ 3px の枠がリンクの内側にちょうど収まり、上下左右の4辺とも枠の色の画素がありました。






