CSSでキーボード操作時だけフォーカス枠を出すには
ボタンやリンクをマウスでクリックしたときにはフォーカスの枠を出さず、Tab キーで移動したときだけ outline で枠を描く。
outline: none で枠を消す代わりに :focus-visible に見た目を指定するので、キーボードで操作する利用者が現在位置を見失わない。
静的確認は、CSS の構文と仕様で確認し、ブラウザでの描画確認までは行っていないものである。
公開:
<div class="demo">
<button type="button" class="btn">保存する</button>
<a class="link" href="#">詳しく見る</a>
<input class="field" type="text" placeholder="名前を入力" aria-label="名前" />
</div>
<p class="hint">Tab キーで移動すると枠が出る。クリックで枠が出るのは入力欄だけだ。</p>.demo {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 16px;
}
.btn {
padding: 8px 16px;
border: 0;
border-radius: 8px;
background: #27272a;
color: #fff;
font: inherit;
cursor: pointer;
}
.link {
border-radius: 4px;
color: #0f766e;
}
.field {
width: 10em;
padding: 8px 10px;
border: 1px solid #71717a;
border-radius: 8px;
font: inherit;
}
/* outline: none で消さず、:focus-visible のときだけ枠を描く。
outline は場所を取らないので、枠が出ても周りの要素は動かない */
.btn:focus-visible,
.link:focus-visible,
.field:focus-visible {
outline: 3px solid #0f766e;
/* 要素との間に隙間を空け、背景色と枠の色が接しないようにする */
outline-offset: 2px;
}
.hint {
margin: 16px 0 0;
font-size: 13px;
color: #52525b;
}フォーカスの枠は :focus ではなく :focus-visible に outline で指定する。
:focus-visible はキーボードで移動したときのように枠が必要だとブラウザが判断した場面でだけ当たるので、クリックのたびに枠が出る煩わしさを避けながら、キーボードで操作する利用者には現在位置を必ず示せるためである。
つまずき
クリックしたときに出る枠を消したくて :focus { outline: none; } と書くと、キーボードで操作する利用者からも枠が消え、いまどこにフォーカスがあるのか分からなくなる。
現在の主要なブラウザは、既定のフォーカス枠そのものを :focus-visible で出しているので、ボタンをクリックしても枠が出ないのが標準の動きである。
クリックで枠が出て困っているなら、枠を消す指定を足す前に、:focus に outline や box-shadow を付ける指定がどこかに書かれていないかを確認するとよい。
そこを :focus-visible に書き換えれば、クリックでは枠が出ず、Tab キーで移動したときには枠が出る状態になる。
枠が出る場面と出ない場面
どの場面で :focus-visible が当たるかはブラウザの判定に任されているが、仕様(Selectors Level 4)には目安が示されている。
Tab キーなどのキーボード操作で移動したときは当たり、マウスやタッチでボタンやリンクを押したときは当たらない。
ただし、入力欄のようにキーボード入力を受け付ける要素は、クリックで選んだときにも当たる。
上のプレビューで入力欄だけがクリックでも枠を出すのはこのためである。
スクリプトで focus() を呼んでフォーカスを移した場合は、直前の要素に枠が出ていたかどうかが引き継がれる。
:focus-visible は 2022 年 3 月から主要なブラウザすべてで使える。
それより古いブラウザにも枠を出す必要があるなら、@supports not selector(:focus-visible) の中に同じ指定を :focus で書いておく。
枠を outline で描く理由
枠は border や box-shadow ではなく outline で描いている。
outline は要素の外側に描かれて場所を取らないので、枠が出てもボタンの大きさや隣の要素の位置は変わらない。
border で描くと太さの分だけ要素が大きくなり、フォーカスが移るたびに周りがずれる。
box-shadow も場所を取らない点は同じだが、Windows のハイコントラストのような強制カラーモードでは none に置き換えられ、枠そのものが消える。
outline は色がシステムの色に置き換わるだけで、枠は残る。
どうしても box-shadow で描きたい場合は outline: 2px solid transparent を併せて書いておくと、強制カラーモードではこの透明な outline が見える色で描かれ、枠の代わりになる。
以前は outline が角丸に沿わないブラウザがあったが、現在の主要なブラウザでは outline も border-radius に沿って丸く描かれる。
見分けやすい枠にする
枠は周りとはっきり区別できる色と太さにする。
WCAG 2.1 の達成基準 1.4.11(非テキストのコントラスト)は、フォーカスの枠のように操作に必要な視覚情報に対して、隣り合う色との 3:1 以上のコントラストを求めている。
上の例の #0f766e は白い背景に対して約 5.5:1 あり、この基準を満たす。
一方、同じ色をボタンの背景 #27272a に直接接して描くと、コントラストは約 2.7:1 に下がる。
outline-offset で要素から 2px 離しているのは、枠の内側に白い隙間を作り、枠が接する色を白にそろえるためである。