logo

フォーカスリングは:focusではなく:focus-visibleで定義する

2026-05-30
3 days ago

開発環境

  • Next.js(App Router)
  • React 19
  • Tailwind CSS v4

前提

ボタンやリンクをキーボードの Tab キーで移動すると、フォーカスされた要素のまわりに枠線が出ます。これがフォーカスリング(outline)で、キーボード操作のユーザーが「今どこにいるか」を知るための大事な目印です。

Tailwind を使っている Next.js のプロジェクトで、デザイン都合でこのリングを調整しようと focus: バリアントを触ったところ、マウス操作でも常にリングが出てしまう挙動にハマりました。

ローカルで npm run dev を立ち上げ、Tab キーとマウスクリックの両方で挙動を見比べながら確認しています。

本題

focus: はマウスでもキーボードでも反応する

⚠️ たとえばボタンのフォーカスリングを自前のスタイルに差し替えようとして、こう書いたとします。

<button className="focus:outline-2 focus:outline-blue-500">
  送信
</button>

focus:(CSS の :focus)は「要素がフォーカスを持っている状態」すべてに当たります。キーボードの Tab で移動したときだけでなく、マウスでクリックしたときにもフォーカスは当たるため、クリックのたびにリングが出ます。

逆に、リングが邪魔だからと消すパターンも厄介です。

{/* マウスユーザーのために消したつもり */}
<button className="focus:outline-none">
  送信
</button>

これだとキーボードユーザーからもリングが消えてしまい、「今どこをフォーカスしているか」がわからなくなります。アクセシビリティ的にはかなり良くない状態です。

focus-visible: はキーボード操作のときだけ当たる

🛠 そこで focus-visible: バリアントを使います。

<button className="focus-visible:outline-2 focus-visible:outline-blue-500">
  送信
</button>

focus-visible:(CSS の :focus-visible)は、ブラウザが「フォーカスリングを見せるべき」と判断したときだけ当たります。判断はブラウザのヒューリスティックに任されていて、ざっくり次のような挙動になります。

  • キーボード(Tab キーなど)でフォーカスした → リングが出る
  • マウスでボタンをクリックしてフォーカスした → リングが出ない

👉 これで「キーボードユーザーには見せる、マウスユーザーには見せない」が、JavaScript なしで実現できます。

実際に npm run dev で動かして、Tab で移動するとリングが出て、マウスクリックでは出ないことをブラウザ上で確認できました。

outline を消したいときの定番パターン

リセットで outline-none を全体にかけてしまうと、キーボードユーザーが迷子になります。消したうえで focus-visible: で出し直すのが安全です。

<button className="outline-none focus-visible:outline-2 focus-visible:outline-blue-500 focus-visible:outline-offset-2">
  送信
</button>

outline-offset-2 を足すと、要素とリングの間に余白ができて見やすくなります。

毎回このクラスを並べるのが冗長なら、共通ボタンコンポーネントに切り出しておくと使い回しやすくなります。

const Button = ({ children, ...props }: React.ComponentProps<'button'>) => (
  <button
    className="outline-none focus-visible:outline-2 focus-visible:outline-blue-500 focus-visible:outline-offset-2"
    {...props}
  >
    {children}
  </button>
)

テキスト入力欄では focus-visible: でも出る

注意点として、focus-visible: は「マウスなら必ず出ない」わけではありません。テキスト入力欄(inputtextarea)はマウスでクリックしてフォーカスしても、ブラウザはリングを出すべきと判断します。

  • ボタンやリンク:マウスクリックではリングが出ない
  • テキスト入力欄:マウスクリックでもリングが出る

これは「入力欄は今どこに文字が入るかを示す必要がある」という判断によるもので、意図どおりの挙動です。focus-visible: に任せておけば、要素の種類に応じていい感じに出し分けてくれます。

さいごに

フォーカスリングは「消すか出すか」の二択で考えがちですが、本当にやりたいのは「マウスのときは出さない、キーボードのときは出す」という出し分けです。

focus: でこれをやろうとすると JavaScript で操作種別を見分ける必要がありましたが、focus-visible: ならブラウザがその判断を肩代わりしてくれます。

Tailwind でフォーカス周りのスタイルを書くときは、まず focus: ではなく focus-visible: を第一候補にする、と覚えておくと迷わなくなりそうです。

参照