thinceller blog

Chakra UIのLinkコンポーネントとfocus-visible疑似クラス

2021-11-28 公開

追記: Safari でも focus-visible がサポートされるようになりました

2022年3月にリリースされたSafari 15.4にて focus-visible 疑似クラスがサポートされるようになりました。

Safari 15.4 Release Notes | Apple Developer Documentation

Chakra UIにおいても、v2.2.0から要素へのフォーカス時のスタイルにデフォルトで focus-visible を使うようになっています。

chakra-ui/CHANGELOG.md at @chakra-ui/[email protected] · chakra-ui/chakra-ui refactor: remove annoying focus outline by segunadebayo · Pull Request #6153 · chakra-ui/chakra-ui

本記事で紹介しているpolyfillも必要なくなったので、現在はこのブログにおいてもpolyfillを削除しています。


このブログでも使用しているChakra UIには多くのコンポーネントがあり、WAI-ARIA仕様に対応しているなどアクセシビリティが考慮されています。 しかし、実際に利用していると気になる点がありました。

Chakra UIの <Link> コンポーネントはchakraによって拡張されたただの <a> タグです。

Link - Chakra UI

Next.jsでのinternalなページ遷移で利用する場合は next/link と併用する必要があります。 以下のコードでは、internalなページのときのみ next/link<Link> をラップし、外部のページのときは素の <Link> を使用する例です。

MyLink.tsx
import NextLink from 'next/link';
import { Link } from '@chakra-ui/react';

const MyLink = (props: JSX.IntrinsicElements['a']) => {
  const { href, ...rest } = props;
  const isInternalLink = href && (href.startsWith('/') || href.startsWith('#'));

  if (isInternalLink) {
    return (
      <NextLink href={href} passHref>
        <Link {...rest} />
      </NextLink>
    );
  }

  return <Link isExternal {...p} />;
};

このコンポーネント自体は問題なく動作するのですが、 ヘッダーのような複数ページに渡って表示される場所に置いてクリックするとfocus時の青いアウトラインがページ遷移後も表示されたまま残ってしまいます。

Chakra UI のリンク

この問題はChakra UIのIssueとして取り上げられています。

Blue outline borders around all clickable components ugly · Issue #708 · chakra-ui/chakra-ui

コメントによれば、Chakra UIはWAI-ARIA規格に厳密に従うことを目標にしており、focus時のアウトラインをデフォルトで無効にすることはない、ということです。 デフォルトで有効になっていること自体は納得できるのですが、Tabキーなどのキーボード操作ではないマウスクリックでもアウトラインが表示されるのは少し気になります。

_focus props を変更してアウトラインを無効化する

先ほどのIssueではいくつかの回避策が提示されています。 そのうちのひとつは、Chakra UIが設定しているfocusのスタイルを上書きする方法です。

個別の <Link> コンポーネントを修正する場合は、_focus propsにスタイルを追加します。

<Link _focus={{ outline: 'none', boxShadow: 'none' }}>Link</Link>

また、<Link> コンポーネント全体でアウトラインを無効化したい場合、themeを拡張することでデフォルトの挙動を変更可能です。

chakraTheme.ts
import { extendTheme } from '@chakra-ui/react';

const theme = extendTheme({
  components: {
    Link: {
      baseStyle: {
        _focus: {
          outline: 'none',
          boxShadow: 'none',
        },
      },
    },
  },
});

これで <Link> クリック時にアウトラインが表示されることはなくなりました。

しかし、この方法はキーボード操作によるfocusでもアウトラインが表示されなくなります。 できれば避けたい気持ちがありますね。

focus-visible 疑似クラスを使う

同Issueで多くリアクションを集めていたのは、focus-visible 疑似クラスを用いた回避方法でした。 以下の記事にまとまっています。

Accessibility on-demand with Chakra-ui and focus-visible | by Keegan Famouss | Medium

MDNのページには以下のように記載されています。

:focus-visible 擬似クラスは、要素が :focus 擬似クラスに一致している時で、ユーザーエージェントが要素にフォーカスを明示するべきであると推測的に判断した場合に適用されます (多くのブラウザーではこの場合、既定で「フォーカスリング」を表示します)。

このセレクターは、ユーザーの入力方法 (マウスなのかキーボードなのか) によって異なるフォーカス表示を提供したい場合に便利です。

:focus-visible - CSS: カスケーディングスタイルシート | MDN

今回の「マウスクリック時はアウトラインを無効化してキーボード操作にはスタイルを適用させたい」というユースケースに合致しています。

残念ながら、2021年11月現在 focus-visible 疑似クラスはSafariが対応していないので、モダンブラウザすべてで使うためにはpolyfillを使う必要があります。

以下、focus-visible - npm を使った設定手順です。

  1. npm install focus-visible or yarn add focus-visible
  2. import 'focus-visible/dist/focus-visible' を配置する
  3. focus-visible CSSをアプリケーションに適用する

Next.js + Chakra UIでの設定例は以下のようになります。

_app.tsx
import { ChakraProvider } from '@chakra-ui/react';
import { Global, css } from '@emotion/react'

import 'focus-visible/dist/focus-visible'

const globalStyles = css`
  .js-focus-visible :focus:not(.focus-visible) {
    outline: none;
    box-shadow: none;
  }
`;

function MyApp({ pageProps }) {
  return (
    <ChakraProvider resetCSS>
      <Global styles={globalStyles} />
      <Component {...pageProps} />
    </ChakraProvider>
  );
}

上記の設定はこのブログサイトにすでに適用されています。 試してみたい場合はこのブログサイトのヘッダーのリンクをクリックしたりTabキーを押したりしてみてください。

まとめ

Chakra UIはアクセシビリティに配慮された使い勝手のいいコンポーネントライブラリですが、 デフォルトの挙動が気に入らない場合はスタイルをオーバーライドして変更可能です。

最新のCSSを活用することでアクセシビリティを維持したまま意図する挙動を実現できるので、polyfillが必要なくなるようにSafariでの実装が待たれます。