Next.js × ChakraUIでフォーカスを調整する

Next.jsでChakraUIを使っての開発時にフォーカスとそのスタイルで意外と気にすることが多かったので紹介します。

## Next.jsでページ間リンクにフォーカスがつかない
Next.jsでサイト内遷移のリンクを作成するとき、`next/link`を使います。

https://nextjs.org/docs/api-reference/next/link

普通に使うとこんな感じですが、
```.jsx
import Link from 'next/link'
export const Hoge = () => {
  return (
    <Link href={'/about'}>
      <a>About Us</a>
    </Link>
  )
}
```
リンクにChakraUIのスタイルを使いたい場合や、ChakraUIの書式でスタイルをつけたい場合は、`a`タグのかわりにChakraUIのリンクコンポーネントを使います。（他のタグに置き換えない場合は`a`タグで出力されます）
```.jsx
import Link from 'next/link'
import { Link as ChakraLink } from '@chakra-ui/react'

export const Hoge = () => {
  return (
    <Link href={'/about'} passHref>
      <ChakraLink>About Us</ChakraLink>
    </Link >
  )
}
```

https://chakra-ui.com/docs/components/navigation/link

このとき気をつけたいのが、`passHref`です。Next.jsのLinkコンポーネントの直下の子要素がコンポーネントである場合、`passHref`を付けないと`a`タグに`href`が出力されません。
```.html
<!-- passHrefがあるとき -->
<a href="/about">About Us</a>
<!-- passHrefがないとき（遷移は普通にできる） -->
<a>About Us</a>
```
> https://nextjs.org/docs/api-reference/next/link#if-the-child-is-a-custom-component-that-wraps-an-a-tag
> If the child is a custom component that wraps an `<a>` tag

そしてhrefがないとフォーカスもつきません。
最初これに気づかずローカル環境だからhrefがないのかな…などと考えていましたが、`passHref`です。
Next.jsが提供しているESLintプラグインにこれをチェックするものも含まれています。適宜使いたいです。

https://nextjs.org/docs/basic-features/eslint#eslint-plugin

## overflow hiddenするとフォーカスが欠ける
ChakraUIのUIコンポーネントのフォーカスは、特にカスタマイズしていない場合外側に`box-shadow`でスタイルがつきます。
このスタイルは`boxShadow='outline'`で任意の場所に使うことも可能です。
```.jsx
<Box boxShadow={'outline'} p={6} rounded={'md'} bg={'white'}>
  Outline
</Box>
```

ChakraUIを使ってカスタムコンポーネントに対してスタイルを書く中で、LinkやButtonの親要素に`overflow: hidden`を指定するとフォーカスのスタイルが要素の外側につくため、フォーカスが一部表示されなくなることがあります。
`boxShadow='outline'`のようなフォーカススタイルでなんとか表示できるようにしたい場合は、`boxShadow`に`inset`をつけると内側にShadowをつけられます。

```.jsx
<Link
  href={'/piyo'}
  _focus={{
    boxShadow: '0 0 0 3px rgba(66, 153, 225, 0.6) inset',
    outline: 'none'
  }}
>
  piyo
</Link>
```
## フォーカススタイルの見た目が違うコンポーネントがある
ChakraUIのコンポーネントを素直に使っている場合は問題ないのですが、`Box`をbuttonにして使ったりすると`Box`にはフォーカス時のスタイルがないので、フォーカスするとブラウザデフォルトの`outline`についたスタイルになります。気にする場合は`_focus`のスタイルを設定します。
```.jsx
<Box
  as={'button'}
  _focus={{
    boxShadow: 'outline'
  }}
>
  hoge
</Box>
```
## input要素にフォーカスがつかない
ChakraUIあまり関係ないです。
`type="file"`のinputにスタイルをつけるとき、`label`タグで囲うとラベルをクリックして動作するinputになるのですが、フォーカスがつきません。labelにはフォーカスしないためです。
buttonなどのフォーカスする要素で見た目を作成し、その操作にinputが連動するような実装にするとフォーカスするUIにできます。

```.jsx
export const FileInput = () => {
  const inputRef = ref as React.MutableRefObject<HTMLInputElement>
  const clickHandler = () => {
    inputRef.current.click()
  }
  return (
    <>
      // フォーカスが表示されない
      <FormLabel cursor={'pointer'}>
        <Input
          type={'file'}
          opacity={0}
          visibility={'hidden'}
          onChange={onChange}
        />
      </FormLabel>

      // フォーカス表示される
      <Box as={'button'} type={'button'} onClick={clickHandler}>ファイル添付</Box>
      <Input
        type={'file'}
        opacity={0}
        visibility={'hidden'}
        ref={ref}
        onChange={onChange}
      />
    </>
  )
}
```
## フォーカスをキーボード操作時のみにしたい

フォーカスをキーボード操作時のみにしたい場合は、`focus-visible`が使えます。

https://developer.mozilla.org/ja/docs/Web/CSS/:focus-visible

ChakraUIのstyle propsにも`_focusVisible`があるので、これを使えば指定できます。

https://chakra-ui.com/docs/styled-system/features/style-props#pseudo

```.jsx
<Tab
  _focus={{
    boxShadow: 'none',
  }}
  _focusVisible={{
    boxShadow: 'outline',
  }}
>
  hoge
</Tab>
```

## まとめ
ChakraUIは元々フォーカスに限らずアクセシビリティに考慮されて作られています。そのためコンポーネントを素直に使えばわりあい快適なUIが実装ができるのですが、自分でカスタムコンポーネントを作り始めるとそうはいかなくなってきます。なるべくうまくやりたいものです。

https://chakra-ui.com/guides/principles