:focus-ringの代用としてwhat-inputを試す

前回の記事で紹介した:focus-ringのポリフィルはイマイチだった。element.focus()で制御したときにいい感じにならないというつぶやきを見て知った。

具体的な例として、モーダルを閉じた後の挙動について考えたい。
モーダルを閉じた後は、フォーカスはモーダルを開いたボタンに戻るように実装する。そのために、button.focus()のようにしてフォーカスをスクリプトで制御する。この際、:focus-ringのポリフィルだとfocus-ringが有効になる処理が実行されず、キーボード操作をしていても適切なスタイルが表示されないことになる。

調べてみるとWhat Input?という似たライブラリがあった。ユーザーの入力端末を検出する機能があり、同じくfocus-ringの制御をすることが目的らしい。:focus-ringのポリフィルとは違い、入力端末の検出結果をhtml要素の属性などを通して公開している。フォーカスのスタイルはそれに基づいて設定すればいい。これを利用すれば、意図した通りにフォーカスのスタイルを機能させることができた。

提供される状態は、initialmousekeyboardtouchのいずれかだ。initialはまだ検出できていないことを示す。マウスとタッチデバイスではoutlineを非表示にしたいので次のようにする。

[data-whatinput="mouse"] :focus,
[data-whatinput="touch"] :focus {
  outline: none;
}

また、E:focus-ringと同じ意図を示すセレクタは次のようになる。

html:not([data-whatinput="mouse"]):not([data-whatinput="touch"]) .awesome-button:focus {
  // focus-ring style
}

ただこれでは冗長だ。E:hoverと併記したいことも考えると、Sassで次のように抽象化できる。

@mixin focus-ring() {
  html:not([data-whatinput="mouse"]):not([data-whatinput="touch"]) &:focus {
    @content;
  }
}

.awesome-button:hover {
  background-color: red;
}

.awesome-button {
  @include focus-ring() {
    @extend .awesome-button:hover;
  }
}

多分これで問題なく:focus-ring風の実装ができるはずだ。将来的に未知の入力端末が登場しても、マウスとタッチデバイス以外ではフォーカスのスタイルが表示されるため、ウェブサイトが操作不能になる可能性は低いと思う。
CSSセレクタが複雑になってしまう問題はあるが、フォーカスのスタイルを非表示にしたいという要望とのトレードオフだろう。

本当は:focus-ringがネイティブで実装される日を待ちたいが、これが今のところの現実解なんだと思う。