CSS の :focus-visible と :focus の違い - キーボード操作時だけアウトラインを出す

ボタンやリンクをクリックしたとき、青いアウトラインが表示されて気になったことはないでしょうか。見た目を整えるために outline: none を指定してしまう人も多いですが、これはキーボードユーザーにとって深刻なアクセシビリティの問題を引き起こします。:focus-visible を使えば、マウス操作時はアウトラインを消しつつ、キーボード操作時にはしっかり表示するという理想的な挙動を実現できます。

:focus と :focus-visible の違い

どちらもフォーカス状態をスタイリングする擬似クラスですが、発火するタイミングが異なります。

:focus

マウスクリック・キーボード操作・タッチ操作など、あらゆる方法でフォーカスが当たったときに適用される

:focus-visible

ブラウザが「フォーカスの可視化が必要」と判断したときだけ適用される。主にキーボード操作(Tab キーなど)でフォーカスが移動した場合に発火する

つまり :focus-visible は、ブラウザがユーザーの入力手段を判別し、キーボード操作のときだけスタイルを当てる仕組みです。マウスでボタンをクリックしたときには発火しないため、不要なアウトラインが表示されません。

なぜ outline: none が危険なのか

デザイン上の理由から、次のように書いてしまうケースがよくあります。

button:focus {
  outline: none;
}

これはすべてのフォーカスインジケーターを消してしまうため、キーボードだけで操作しているユーザーは、今どの要素にフォーカスが当たっているのかわからなくなります。視覚障害のあるユーザーやマウスを使えない環境では、ページの操作が事実上不可能になることもあります。

outline: none を :focus に指定

すべてのフォーカスインジケーターが消える

キーボードユーザーが現在位置を見失う

:focus-visible を使った正しいアプローチ

:focus-visible を使えば、マウスクリック時のアウトラインだけを消し、キーボード操作時のアウトラインは残すことができます。

button:focus {
  outline: none;
}

button:focus-visible {
  outline: 2px solid #4f46e5;
  outline-offset: 2px;
}

この書き方では、まず :focus ですべてのアウトラインを消し、次に :focus-visible でキーボード操作時のみカスタムアウトラインを表示しています。マウスでクリックしたときは :focus-visible が発火しないため、アウトラインは表示されません。

HTML
CSS
JavaScript
<div class="demo">
  <p class="demo-label">マウスクリックとTabキーで違いを確認</p>
  <button class="btn-bad">:focus だけ(outline: none)</button>
  <button class="btn-good">:focus-visible を使用</button>
</div>
.demo {
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: flex-start;
}
.demo-label {
  font-size: 13px;
  color: #666;
  margin: 0;
}
.btn-bad, .btn-good {
  padding: 10px 24px;
  font-size: 15px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  cursor: pointer;
  background: #fff;
}
.btn-bad:focus {
  outline: none;
}
.btn-good:focus {
  outline: none;
}
.btn-good:focus-visible {
  outline: 2px solid #4f46e5;
  outline-offset: 2px;
}

上のボタンはフォーカスインジケーターが完全に消えているため、Tab キーで移動してもどちらにフォーカスがあるかわかりません。下のボタンは Tab キーでフォーカスすると紫のアウトラインが表示され、マウスクリックでは表示されません。

input 要素での挙動の違い

テキスト入力欄(input[type=“text”] や textarea)では、:focus-visible の挙動がボタンとは異なる点に注意が必要です。テキスト入力欄はクリックしてもキーボード入力が前提となるため、ブラウザはマウスクリック時でも :focus-visible を発火させます。

要素マウスクリック時Tab キー移動時
button:focus のみ:focus と :focus-visible
input[type="text"]:focus と :focus-visible:focus と :focus-visible
a(リンク):focus のみ:focus と :focus-visible

テキスト入力欄では常に :focus-visible が発火するため、ボタンと同じ感覚で「マウスクリック時はアウトラインを消す」という使い方はできません。ただし入力欄にはフォーカスインジケーターを常時表示するのが一般的なので、これは自然な挙動といえます。

outline-offset でアウトラインの位置を調整する

:focus-visible と一緒に使うと便利なのが outline-offset プロパティです。アウトラインと要素の間に余白を設けることで、デザインに馴染みやすいフォーカスインジケーターを作れます。

button:focus-visible {
  outline: 2px solid #4f46e5;
  outline-offset: 3px;
}
outline-offset: 0(デフォルト)

アウトラインが要素の境界にぴったり沿って表示されます。border と重なるため、見づらくなることがあります。

outline-offset: 正の値

アウトラインが要素の外側に離れて表示されます。2px〜4px 程度がよく使われ、要素との間に余白ができて視認性が上がります。

outline-offset: 負の値

アウトラインが要素の内側に食い込んで表示されます。背景色のある要素で、アウトラインを内側に引きたい場合に使うことがあります。

実践的なリセット CSS のパターン

プロジェクト全体でフォーカススタイルを統一するには、リセット CSS の段階で :focus-visible を設定しておくのが効率的です。

:focus {
  outline: none;
}

:focus-visible {
  outline: 2px solid var(--focus-color, #4f46e5);
  outline-offset: 2px;
  border-radius: 2px;
}

CSS カスタムプロパティ(–focus-color)を使うことで、コンポーネントごとにフォーカスカラーを変えたい場合にも柔軟に対応できます。ダークモードでは明るめの色、ライトモードでは濃いめの色を指定するといった切り替えも容易になります。

ブラウザ対応状況

:focus-visible はモダンブラウザではすでに広くサポートされています。Chrome 86 以降、Firefox 85 以降、Safari 15.4 以降、Edge 86 以降で利用可能です。IE 11 はサポートしていませんが、IE のサポート終了を考えると、現在のプロジェクトで問題になることはほぼありません。

古いブラウザへのフォールバックが必要な場合は、:focus のスタイルを先に書いておき、:focus-visible 対応ブラウザでは上書きする形にします。非対応ブラウザでは :focus-visible の宣言が無視され、:focus のスタイルがそのまま残るため、フォーカスインジケーターが完全に消えてしまう事態を防げます。

:focus をベースに、:focus-visible で上書きするプログレッシブ・エンハンスメント戦略。

アクセシビリティを損なわずに見た目を整えるという、かつては JavaScript でしか実現できなかったことが、:focus-visible ひとつで解決できるようになりました。新しいプロジェクトではリセット CSS に組み込んでおくのがおすすめです。