- CSS
CSS focusとfocus-visibleの違い outlineの代替
:focus は「フォーカスが当たっている要素すべて」に一致し、:focus-visible は「そのうち、ブラウザがフォーカスの位置を見せたほうがよいと判断したとき」だけに一致する擬似クラスです。 ボタンをマウスでクリックしたときは :focus だけが一致し、Tab キーで移動してきたときは :focus と :focus-visible の両方が一致します。この差を使うと、「クリックしたときの枠線は出さず、キーボード操作のときだけフォーカスリングを出す」というデザインを、outline: none で枠線を消してしまうことなく作れます。
この記事では、3つの擬似クラス(:focus/:focus-visible/:focus-within)がどの操作・どの要素で一致するかを Chromium 141 で実際に動かして表にしました。あわせて、outline: none の代わりにそのまま貼れるフォーカスリングの CSS(outline-offset、box-shadow 併用、forced-colors 対応)、:focus-within によるフォームの強調、:has(:focus-visible) の使い方を、算出スタイルの実測値つきで紹介します。
フォーカス系擬似クラスの早見表
| 擬似クラス | 一致する要素 | 一致するタイミング | 主な使いどころ |
|---|---|---|---|
:focus |
フォーカスを持つ要素そのもの | マウス・キーボード・タッチ・スクリプト、どの方法でもフォーカスが当たれば一致 | 入力欄の枠色を変えるなど、操作方法を問わず見せたい変化 |
:focus-visible |
フォーカスを持つ要素そのもの | :focus に一致し、かつブラウザが「位置を示すべき」と判断したとき(キーボード操作、文字入力欄など) |
フォーカスリング(outline)の指定。まずこれを使う |
:focus-within |
フォーカスを持つ要素と、その祖先すべて | 自分か子孫のどれかが :focus に一致している間 |
フォームの行・カード・検索ボックス全体の強調 |
:has(:focus-visible) |
子孫に :focus-visible の要素を持つ祖先 |
子孫がキーボード操作などでフォーカスされている間 | 「キーボード操作のときだけ親を強調」したいとき |
迷ったときの選び方は次のとおりです。
| やりたいこと | 書くセレクタ |
|---|---|
| フォーカスリングを自分のデザインにしたい | :focus-visible |
| クリック時の枠線は出さず、Tab 移動のときだけ出したい | :focus-visible(:focus には outline を書かない) |
| 入力欄はクリックでも枠色を変えたい | :focus でも :focus-visible でも可(入力欄はどちらもクリックで一致) |
| 入力中の行やラベルごと背景色を変えたい | 親に :focus-within |
| キーボードで中のボタンに来たときだけカードを強調したい | 親に :has(:focus-visible) |
操作方法と要素ごとの一致表(Chromium 141 で実測)
「マウスでクリック」「Tab キーで移動」「タッチでタップ」の3つの操作を、button・a・input などに対して行い、その直後に element.matches() で各擬似クラスに一致するかを調べました。
- 環境:Playwright 1.56.0 + Chromium 141.0.7390.37(ヘッドレス)、Node.js 22.22
- タッチは Playwright のタッチ入力(
hasTouch: trueのコンテキストでtouchscreen.tap())による再現です。実機のスマートフォンでは未実行です - Firefox・Safari では未実行です。:focus-visible の判定はブラウザの判断に任されているため、細部はブラウザによって変わる可能性があります
button・a・input の結果
○は一致、×は一致しなかったことを表します。:focus-within は、対象要素を囲む親の div で調べた結果です。
| 操作 | 要素 | :focus |
:focus-visible |
親の :focus-within |
|---|---|---|---|---|
| マウスでクリック | button |
○ | × | ○ |
| マウスでクリック | a(href あり) |
○ | × | ○ |
| マウスでクリック | input(テキスト) |
○ | ○ | ○ |
| Tab キーで移動 | button |
○ | ○ | ○ |
| Tab キーで移動 | a(href あり) |
○ | ○ | ○ |
| Tab キーで移動 | input(テキスト) |
○ | ○ | ○ |
| タッチでタップ | button |
○ | × | ○ |
| タッチでタップ | a(href あり) |
○ | × | ○ |
| タッチでタップ | input(テキスト) |
○ | ○ | ○ |
ポイントは「button と a はポインター操作(マウス・タッチ)では :focus-visible に一致せず、input のテキスト欄はどの操作でも一致する」ことです。 文字を打つ要素は、キーボードから入力する場所が分かるように、クリックでもフォーカスの表示が必要だと判断されています。
そのほかの要素の結果
同じ方法で、ほかのフォーム部品も調べました。
| 要素 | マウスでクリック時の :focus-visible |
タッチ時の :focus-visible |
Tab 移動時の :focus-visible |
|---|---|---|---|
textarea |
○ | ○ | ○ |
select |
○ | ○ | ○ |
input type="text"(readonly) |
○ | ○ | 未実行 |
input type="checkbox" |
× | × | ○ |
input type="radio" |
× | × | 未実行 |
input type="submit" |
× | × | 未実行 |
div tabindex="0" |
× | × | ○ |
チェックボックス・ラジオボタン・送信ボタンは、input 要素であってもボタンと同じ扱いで、ポインター操作では一致しませんでした。「input だからクリックで :focus-visible になる」のではなく、「文字入力を受け付ける部品だからなる」 と覚えると整理しやすくなります。
スクリプトで focus() したとき
モーダルを開いたあとに element.focus() でフォーカスを移す、といった場面の結果です。
| 直前の状態 | button.focus() 後の :focus-visible |
|---|---|
ボタン A をマウスでクリック → その click 処理の中でボタン B に focus() |
×(一致しない) |
ボタン A を Tab + Enter で押す → その click 処理の中でボタン B に focus() |
○(一致する) |
どの要素にもフォーカスがない状態(本文をクリックした直後)で focus() |
○(一致する) |
直前にフォーカスを持っていた要素の状態を引き継ぐ動きになっています。マウスで開いたモーダルではリングが出ず、キーボードで開いたモーダルではリングが出るので、利用者の操作方法に合った表示になります。
検証に使ったコード
次のスクリプトで表の結果を取りました。作業フォルダで npm i playwright@1.56.0 を実行してから node focus-test.mjs で動かします(ブラウザ本体が未導入の環境では npx playwright install chromium も必要です)。
// focus-test.mjs
import { chromium } from 'playwright';
const html = `<!doctype html><meta charset="utf-8">
<div id="wrap">
<p><button id="btn">button</button></p>
<p><a id="lnk" href="#x">link</a></p>
<p><input id="inp" value="input"></p>
</div>`;
const ids = ['btn', 'lnk', 'inp'];
const browser = await chromium.launch();
console.log('chromium', browser.version());
for (const mode of ['mouse', 'keyboard', 'touch']) {
for (const id of ids) {
const ctx = await browser.newContext({ hasTouch: mode === 'touch' });
const page = await ctx.newPage();
await page.setContent(html);
const box = await page.locator('#' + id).boundingBox();
const x = box.x + box.width / 2, y = box.y + box.height / 2;
if (mode === 'mouse') await page.mouse.click(x, y);
if (mode === 'touch') await page.touchscreen.tap(x, y);
if (mode === 'keyboard') {
for (let i = 0; i <= ids.indexOf(id); i++) await page.keyboard.press('Tab');
}
const result = await page.evaluate((id) => {
const el = document.getElementById(id);
const wrap = document.getElementById('wrap');
return [el.matches(':focus'), el.matches(':focus-visible'), wrap.matches(':focus-within')]
.map((b) => (b ? 'o' : 'x')).join(' ');
}, id);
console.log(mode.padEnd(9), id.padEnd(4), result);
await ctx.close();
}
}
await browser.close();
実行結果(左から :focus、:focus-visible、親の :focus-within):
chromium 141.0.7390.37
mouse btn o x o
mouse lnk o x o
mouse inp o o o
keyboard btn o o o
keyboard lnk o o o
keyboard inp o o o
touch btn o x o
touch lnk o x o
touch inp o o o
:focus と :focus-visible の違いをコードで確かめる
仕組みが分かったところで、見た目の違いを最小の例で確認します。
<!doctype html>
<meta charset="utf-8">
<style>
.a:focus { outline: 3px solid #d0401a; } /* どの操作でも出る */
.b:focus-visible { outline: 3px solid #1a5fd0; } /* キーボード操作のときだけ出る */
</style>
<button class="a">:focus のボタン</button>
<button class="b">:focus-visible のボタン</button>
| 操作 | .a(:focus) |
.b(:focus-visible) |
|---|---|---|
| マウスでクリック | 赤い枠が出る | 枠は出ない |
| Tab キーで移動 | 赤い枠が出る | 青い枠が出る |
.a のように :focus に outline を書くと、クリックのたびに枠が出ます。「クリックしたら枠が出て気になる」という理由で outline: none を書きたくなるのは、たいていこの形になっているときです。:focus-visible に書き換えれば、枠を消さずに、必要なときだけ出す形にできます。
ブラウザの初期スタイルも :focus-visible 相当
何も CSS を書いていないページで、Tab キーでボタンと入力欄にフォーカスを当て、算出スタイルを調べました(Chromium 141)。
button: outlineStyle: auto | outlineWidth: 1px | outlineColor: rgb(16, 16, 16) | outlineOffset: 0px
input : outlineStyle: auto | outlineWidth: 1px | outlineColor: rgb(16, 16, 16) | outlineOffset: 0px
同じボタンをマウスでクリックした場合は outlineStyle: none のままでした。つまり現在の Chromium では、標準のフォーカスリング自体が「キーボード操作のときだけ出る」作りになっています。outline-style: auto はブラウザ任せのリング(Chromium では二重線風の表示)を意味する値です。
MDN の :focus-visible のページでは、この擬似クラスは 2022年3月以降、主要ブラウザで広く使える(Baseline Widely available)と案内されています。
outline:none の代わりに使えるフォーカスリング CSS
outline: none だけを書くと、キーボードで操作している人は「今どこにいるか」が分からなくなります。消す代わりに、:focus-visible に自分のデザインのリングを指定するのが基本です。ここからは、そのまま貼って使える形で紹介します。
基本形:outline と outline-offset
/* 基本のフォーカスリング */
:focus-visible {
outline: 3px solid #1a5fd0;
outline-offset: 2px;
}
これだけで、ボタン・リンク・入力欄・tabindex を付けた要素のすべてに同じリングが付きます。
| プロパティ | 役割 | 例 |
|---|---|---|
outline-width |
線の太さ | 2px〜3px が見やすい |
outline-style |
線の種類 | solid、dashed、auto(ブラウザ任せ)など |
outline-color |
線の色 | #1a5fd0、currentColor、Highlight など |
outline-offset |
要素の端からリングまでの距離 | 2px で外側に離す。負の値で内側に入る |
outline |
上の3つ(幅・種類・色)の一括指定 | outline: 3px solid #1a5fd0; |
outline は border と違い、ボックスの大きさに含まれないので、フォーカスが当たってもレイアウトが動きません。outline-offset は一括指定の outline には含まれないため、別の行で書きます。
box-shadow を併用して、背景色を問わず見やすくする
濃い背景と薄い背景の両方にボタンが置かれるサイトでは、1色のリングだと片方で見えにくくなります。outline に加えて box-shadow で薄い色の輪を重ねると、どちらの背景でも輪郭が分かります。
/* outline + box-shadow の二重リング */
.focus-ring:focus-visible {
outline: 3px solid #1a5fd0;
outline-offset: 2px;
box-shadow: 0 0 0 6px rgba(26, 95, 208, 0.25);
}
Tab キーでボタンにフォーカスを当てたときと、マウスでクリックしたときの算出スタイルです。
Tab キー: outlineStyle: solid | outlineWidth: 3px | outlineColor: rgb(26, 95, 208) | outlineOffset: 2px | boxShadow: rgba(26, 95, 208, 0.25) 0px 0px 0px 6px
クリック: outlineStyle: none | outlineWidth: 0px | outlineColor: rgb(0, 0, 0) | outlineOffset: 0px | boxShadow: none
キーボード操作のときだけ、outline と box-shadow の両方が付いていることが分かります。
forced-colors(強制カラーモード)に対応する
Windows の「コントラスト テーマ」などを有効にすると、ブラウザは強制カラーモードになり、色がシステムの配色に置き換えられます。このとき box-shadow は描画されなくなるため、box-shadow だけでリングを作っていると、リングが見えなくなります。
Playwright の page.emulateMedia({ forcedColors: 'active' }) で強制カラーモードを再現し、次の2つの書き方を比べました。
/* 書き方 A:box-shadow だけでリングを作る */
button:focus-visible {
outline: none;
box-shadow: 0 0 0 3px #1a5fd0;
}
/* 書き方 B:透明な outline を残しておく */
.t:focus-visible {
outline: 2px solid transparent;
box-shadow: 0 0 0 3px #1a5fd0;
}
強制カラーモードでの算出スタイル:
書き方 A: outlineStyle: none | boxShadow: none
書き方 B: outlineStyle: solid | outlineColor: rgba(5, 0, 73, 0.8) | boxShadow: none
書き方 A では outline も box-shadow もなくなり、フォーカスの位置を示すものが残りません。書き方 B では、透明だった outline の色がシステムの色に置き換わり、リングとして見えるようになります。box-shadow でリングを描きたいときは、outline: none ではなく outline: 2px solid transparent を書いておくと、強制カラーモードでも位置が伝わります。
メディアクエリで明示的に書き分ける方法もあります。
.focus-ring:focus-visible {
outline: 3px solid #1a5fd0;
outline-offset: 2px;
box-shadow: 0 0 0 6px rgba(26, 95, 208, 0.25);
}
@media (forced-colors: active) {
.focus-ring:focus-visible {
outline: 3px solid Highlight; /* システムの強調色 */
box-shadow: none;
}
}
強制カラーモードを再現した状態での算出スタイルは次のとおりでした(matchMedia('(forced-colors: active)').matches は true)。
outlineStyle: solid | outlineWidth: 3px | outlineColor: rgba(5, 0, 73, 0.8) | outlineOffset: 2px | boxShadow: none
Highlight はシステムカラーのキーワードで、実際の色は利用者のテーマによって決まります。上の rgba(5, 0, 73, 0.8) は検証環境の Chromium が返した値で、Windows の実機での表示色は未実行です。
コピペ用:サイト全体に入れるフォーカスリング一式
ここまでの内容をまとめた、サイト共通の CSS です。色は CSS 変数で差し替えられるようにしています。
/* ===== フォーカスリング一式(そのまま貼れます) ===== */
:root {
--focus-color: #1a5fd0; /* リングの色 */
--focus-halo: rgba(26, 95, 208, 0.25); /* 外側の薄い輪 */
--focus-width: 3px;
--focus-offset: 2px;
}
/* 1. キーボード操作などのときだけリングを出す */
:focus-visible {
outline: var(--focus-width) solid var(--focus-color);
outline-offset: var(--focus-offset);
}
/* 2. ボタン・リンクには薄い輪を重ねて背景色を問わず見やすくする */
:is(button, a, [role="button"], summary):focus-visible {
box-shadow: 0 0 0 calc(var(--focus-width) + var(--focus-offset) + 1px) var(--focus-halo);
}
/* 3. 入力欄は内側にリングを入れて、隣の部品と重ならないようにする */
:is(input, textarea, select):focus-visible {
outline-offset: 0;
}
/* 4. 濃い背景の領域では色だけ差し替える */
.on-dark {
--focus-color: #ffd54a;
--focus-halo: rgba(255, 213, 74, 0.35);
}
/* 5. 強制カラーモードではシステムの色に任せる */
@media (forced-colors: active) {
:focus-visible {
outline-color: Highlight;
box-shadow: none;
}
}
この一式は、上で確かめた「outline + outline-offset」「box-shadow 併用」「forced-colors での切り替え」を組み合わせたものです。一式そのものを全要素で検証したわけではなく、各部分の動きを個別に確認しています。
直す前と後:outline:none を書いていた CSS
よく見かける書き方を、:focus-visible を使った形に直す例です。
直す前:
/* クリック時の枠が気になるので消している */
button:focus {
outline: none;
}
直した後:
/* クリック時は出さず、キーボード操作のときだけ出す */
button:focus-visible {
outline: 3px solid #1a5fd0;
outline-offset: 2px;
}
リセット CSS やライブラリがすでに :focus { outline: none; } を入れていて、その行を消せない場合は、あとから :focus-visible で上書きします。
/* 既存の :focus { outline: none; } より後ろに置く */
:focus-visible {
outline: 3px solid #1a5fd0;
outline-offset: 2px;
}
:focus と :focus-visible はどちらも擬似クラス1つ分の詳細度なので、後ろに書いたほうが有効になります。逆に、「:focus で付けている枠を、ポインター操作のときだけ外したい」場合は次の形が使えます。
/* :focus の枠を、:focus-visible に一致しないときだけ外す */
button:focus:not(:focus-visible) {
outline: none;
}
:focus-within でフォームの行を強調する
:focus-within は、自分の中のどこかにフォーカスがある間、親要素に一致する擬似クラスです。入力欄そのものではなく、ラベルを含む行全体や、アイコン付きの検索ボックス全体を強調したいときに向いています。
<!doctype html>
<meta charset="utf-8">
<style>
.field {
padding: 8px;
border: 1px solid #ccc;
border-radius: 6px;
transition: border-color .15s, background-color .15s;
}
.field:focus-within {
border-color: #1a5fd0;
background: #eef4ff;
}
.field:focus-within label {
color: #1a5fd0;
font-weight: bold;
}
</style>
<div class="field">
<label for="name">お名前</label>
<input id="name" name="name">
</div>
<div class="field">
<label for="memo">メモ</label>
<textarea id="memo" name="memo"></textarea>
</div>
入力欄をクリックする前と後で、親の .field の算出スタイルを調べた結果です。
クリック前: borderTopColor: rgb(204, 204, 204) | backgroundColor: rgba(0, 0, 0, 0)
クリック後: borderTopColor: rgb(26, 95, 208) | backgroundColor: rgb(238, 244, 255)
入力欄にフォーカスが入ると、親の枠色と背景色が切り替わっています。JavaScript で focus/blur イベントを拾ってクラスを付け外しする処理が、CSS の1ルールで書けます。
検索ボックス全体をひとつの入力欄のように見せる
アイコンと入力欄とボタンを横に並べ、外側の枠にリングを出す例です。内側の入力欄の outline は外し、代わりに親にリングを付けるので、フォーカスの位置は見えたままになります。
<style>
.search {
display: flex;
gap: 4px;
padding: 4px;
border: 1px solid #999;
border-radius: 999px;
}
.search input {
flex: 1;
border: 0;
outline: none; /* 親がリングを出すので内側は外す */
background: transparent;
}
.search:focus-within {
outline: 3px solid #1a5fd0;
outline-offset: 2px;
}
</style>
<form class="search" role="search">
<input type="search" name="q" aria-label="サイト内検索">
<button type="submit">検索</button>
</form>
outline: none を書いてよいのは、このように別の方法でフォーカスの位置を示している場合です。なお、この例では中の「検索」ボタンをマウスでクリックしたときにも、親の :focus-within が一致して外側のリングが出ます。それを避けたい場合は、次の :has(:focus-visible) を使います。
:focus-within を使うときの注意点
| 状況 | 起こること | 対応 |
|---|---|---|
| 親の中にボタンやリンクもある | それらをクリックしたときも親が強調される | 入力欄だけに反応させたいなら :has(input:focus) などに絞る |
| 祖先すべてに一致する | body:focus-within も一致する |
クラスを付けた要素に限定して書く |
display: none の要素 |
フォーカスできないので一致しない | メニューの開閉に使うときは visibility や opacity と組み合わせる |
:has(:focus-visible) でキーボード操作のときだけ親を強調する
:focus-within は、マウスでクリックしたときにも一致します。「キーボードで中のボタンに来たときだけ、カードを目立たせたい」という場合は、:has() と :focus-visible を組み合わせます。
<!doctype html>
<meta charset="utf-8">
<style>
.card {
padding: 8px;
border: 1px solid #ccc;
border-radius: 8px;
}
/* 中の要素が :focus-visible のときだけ、カードの枠色を変える */
.card:has(:focus-visible) {
border-color: #d0401a;
}
</style>
<div class="card">
<h3>備品の貸出申請</h3>
<button type="button">詳細を見る</button>
</div>
カードの中のボタンをマウスでクリックした場合と、Tab キーで移動してきた場合の、カードの border-top-color です。
マウスでクリック: rgb(204, 204, 204) ← 変化なし
Tab キーで移動 : rgb(208, 64, 26) ← 枠色が変わる
CSS.supports('selector(:has(:focus-visible))') は Chromium 141 で true を返しました。
| 書き方 | マウスで中のボタンをクリック | Tab キーで中のボタンへ移動 |
|---|---|---|
.card:focus-within |
一致する | 一致する |
.card:has(:focus-visible) |
一致しない | 一致する |
.card:has(input:focus) |
一致しない(ボタンは対象外) | 一致しない(ボタンは対象外) |
カード全体をリンクのように見せる UI では、内側のリンクのリングを外してカード側にリングを出す、という使い方もできます。
.card:has(a:focus-visible) {
outline: 3px solid #1a5fd0;
outline-offset: 2px;
}
.card a:focus-visible {
outline: none; /* カード側にリングがあるので内側は外す */
}
この2ルールの組み合わせは、上の実測(:has(:focus-visible) の一致条件)をもとにした応用例で、この形そのものの表示確認は未実行です。
フォーカスリングを見やすくするための目安
MDN の :focus-visible のページでは、アクセシビリティの観点として次の点が挙げられています。
- フォーカスの表示は、弱視の人や明るい屋外で画面を見る人にも見える必要がある
- WCAG 2.1 の達成基準 1.4.11 では、フォーカスの表示に少なくとも 3:1 のコントラスト比が求められる
- キーボードとマウスを併用する人にとっては、表示が出たり出なかったりすることが分かりにくく感じられる場合がある
この内容を踏まえた、リングの作り方の目安です。
| 項目 | 目安 | 理由 |
|---|---|---|
| 太さ | 2px 以上(3px が見やすい) | 細い線は小さな画面で見落としやすい |
| 色 | 背景と 3:1 以上のコントラスト | WCAG 2.1 達成基準 1.4.11 |
| 離す距離 | outline-offset: 2px 前後 |
ボタンの背景色とリングが重ならず、輪郭が分かる |
| 濃い背景と薄い背景の両方 | 2色のリング(outline + box-shadow)か、領域ごとに CSS 変数で色を差し替え | 1色ではどちらかで見えにくくなる |
| 強制カラーモード | outline を残す(transparent でも可) |
box-shadow は描画されない |
| 入力欄 | クリックでもリングが出てよい | 文字を打つ場所を示すため、ブラウザも表示する判断をしている |
よくあるつまずきと直し方
| 症状 | 原因 | 直し方 |
|---|---|---|
| クリックするたびに枠が出る | :focus に outline を書いている |
:focus-visible に書き換える |
| Tab キーで移動しても枠が出ない | どこかで :focus { outline: none; } や * { outline: 0; } が効いている |
その行より後ろに :focus-visible { outline: ...; } を置く |
| 入力欄だけクリックでも枠が出る | 文字入力欄はポインター操作でも :focus-visible に一致する |
仕様どおりの動き。色や太さを調整して見た目を整える |
| 強制カラーモードでリングが消える | box-shadow だけでリングを作り、outline: none にしている |
outline: 2px solid transparent を残す |
リングが親の overflow: hidden で切れる |
outline は要素の外側に描かれる | outline-offset を負の値にして内側に描く、または親に余白を取る |
| :focus-within がクリックでも反応する | :focus-within は操作方法を区別しない | :has(:focus-visible) に変える |
div にフォーカスが当たらない |
フォーカスできない要素である | tabindex="0" を付けるか、button や a を使う |
実装後のチェックリスト
- [ ] マウスを使わず、Tab と Shift+Tab だけでページ内を一巡できる
- [ ] どの要素にいるときも、フォーカスの位置がひと目で分かる
- [ ] ボタンをマウスでクリックしたときに、意図しない枠が残らない
- [ ] 濃い背景・薄い背景の両方でリングが見える
- [ ]
outline: none/outline: 0を検索し、代わりの表示がない箇所が残っていない - [ ] 開発者ツールの「forced-colors: active」の再現で、リングが見える
よくある質問
Q. :focus と :focus-visible は、どちらを使えばよいですか?
A. フォーカスリング(outline)の指定には :focus-visible を使うのがおすすめです。クリック時には出ず、キーボード操作のときだけ出るので、outline: none で消す必要がなくなります。入力欄の枠色のように、操作方法を問わず変えたいものには :focus が使えます。
Q. input はマウスでクリックしても :focus-visible になるのはなぜですか? A. 文字入力を受け付ける要素では、キーボードから入力する場所を示す必要があるとブラウザが判断するためです。今回の検証でも、テキストの input・textarea・select はクリックとタップで一致し、checkbox・radio・submit は一致しませんでした。
Q. outline:none を書いてもよいのは、どんなときですか?
A. 別の方法でフォーカスの位置を示しているときです。たとえば親要素に :focus-within や :has(:focus-visible) でリングを出している場合や、box-shadow でリングを描いている場合です。後者は強制カラーモードで box-shadow が描画されないので、outline: none ではなく outline: 2px solid transparent にしておくと表示が残ります。
Q. :focus-within と :has(:focus) は何が違いますか?
A. 「中にフォーカスがあれば一致する」という点はほぼ同じですが、:focus-within はフォーカスを持つ要素自身にも一致します。:has() は中身の条件を細かく書けるので、:has(input:focus) のように対象を絞ったり、:has(:focus-visible) でキーボード操作のときだけに限定したりできます。
まとめ
- :focus はどの操作でも一致、:focus-visible はブラウザが位置を示すべきと判断したときだけ一致する
- Chromium 141 の実測では、button と a はマウス・タッチで :focus-visible に一致せず、テキスト入力欄はどの操作でも一致した
- フォーカスリングは :focus-visible に outline と outline-offset を書くのが基本。
outline: noneで消す必要がなくなる - box-shadow を併用すると背景色を問わず見やすくなる。強制カラーモードでは box-shadow が描画されないので outline を残す
- :focus-within は親の強調に使え、JavaScript なしでフォームの行や検索ボックス全体を強調できる
- :has(:focus-visible) なら、キーボード操作のときだけ親を強調できる
- リングは 2px 以上・コントラスト比 3:1 以上を目安にし、Tab キーだけで一巡して確かめる
参考
- MDN Web Docs「:focus-visible」(Baseline の時期、WCAG 2.1 達成基準 1.4.11 の記述を確認)
- 検証環境:Playwright 1.56.0、Chromium 141.0.7390.37(ヘッドレス)、Node.js 22.22
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。