こんにちは、21新卒のフロントエンドのTepy(テッピー)です。
お読み頂いた方がいるかもしれませんが、先日こちらのReactの便利なライブラリーを紹介する記事を書きました。今回もReact業界での良いライブラリーをもう一つ紹介したいと思います。
本記事で紹介したいのは Reach UI
という UI
ライブラリーです。PR TIMESの企業詳細ページリプレイスプロジェクトにReach UIを利用していくつかのコンポーネントを作成しました。利用するきっかけや実装中の感想などの経験をお伝えらればと思います。
Reach UIとは?
Reach UI seeks to become the accessible foundation of your React-based design system.
Reach UIのサイトから引用すると、Reach UIはReactベースのデザインシステムを作成するためのアクセシビリティを注目した基盤を目指しているライブラリーです。つまり、我々が作ろうとするデザインシステムのコンポーネントの基盤となる最低限のコンポーネントライブラリーとも言えます。
Reach UIと他のUIライブラリーの違い
Reach UIは UI
の言葉が付いていますが、 Material UI
、Chakra UI
のような本格的な(完全な)UIライブラリーではないです。Material UI、Chakra UIなどのライブラリーはある程度のスタイルが入っているコンポーネントが存在し、利用者はそのままコンポーネントを利用してもスタイルが入ったような実装になります。一方で、Reach UIのコンポーネントはアクセシビリティとブラウザーのデフォルトのスタイル以外はほとんどスタイル無しのコンポーネントです。そのため、Reach UIをそのままに利用すると、スタイル無しの実装になります。もちろんスタイルカスタマイズは可能で、単純にできます。(実装のセクションに詳しく説明します)
なぜ Reach UI?
上にReach UIはそのまま利用できない(スタイル無しなので)と書いてありますが、実際にその逆です。個人開発などのソロプロジェクトには確かにMaterial UIやChakra UIのほうが効率が良いかもしれませんが、プロジェクトが大きくなりデザインシステムなどを作るつもりの場合、スタイル無しのようなReach UIの方がより有効的です。何故かというと、Material UIやChakra UIなどを利用して、デザインシステムを作ろうとすると、何処かにデフォルトスタイルのカスタマイズの壁にぶつかる可能性が高いです。もちろん、その壁を超える解決があると思いますが、その壁をぶつかる前に避ける方法があれば、その方法を選択すべきだと思います。その方法のーつはReach UIを利用するのです。
Reach UIのメリット
- アクセシビリティが良い
- スタイル無しでカスタマイズが柔軟
- ウェブのベストプラクティスの機能を提供する
- 構成可能なコンポーネント
Reach UIのデメリット
- コンポーネントのタイプが少ない
- スタイルがないのでスタイルを追加する必要がある
PR TIMESでのReach UIの実装事例
PR TIMESでは、Reactでリプレイスした企業詳細ページのプロジェクトとプレスキットという新機能追加プロジェクトにReach UIのコンポーネントを活用しました。利用したコンポーネントはTab
とListMenu
です。
Tab
ざっくりの実装は以下になります。注目して欲しい所は Reach UI
の composable
な所です。CompanyTab
はTabs, TabPanels, TabPanel, TabList, Tab
のような小さいコンポーネントから作られています。アクセシビリティ用スタイル以外はスタイルが付いていないため、css={topTabPanels}
のような emotion
スタイルでカスタマイズできます。
また、以下の画像のように、CompanyTab
のタブによってコンテンツの位置が違います。プレスリリースのタブだけは SearchBox
と Dropdown
が入っています。普通の実装だと、active
のタブのindex == 0
をチェックすれば、SearchBox
とDropdown
をプレスリリースタブだけに追加できますが、Reach UIのcomposable
の特徴で(コンテンツをどこに配置しても同じ動作になります)、SearchBox
とDropdown
をUIの並び順と同じように実装できます。


import { Tabs, TabPanels, TabPanel, TabList, Tab } from '@reach/tabs';
import { SearchBox, TabTitle } from '@/components/pc';
export const CompanyTab = () => {
return (
<Tabs
index={currentTab}
onChange={(index) => setCurrentTab(index)}
>
{/*
SearchBoxとDropdownメニュー表示
UIの並び順と同じように実装
*/}
<TabPanels css={topTabPanels}>
<TabPanel css={topTabPanel}>
<div css={topTabPanelWrapper}>
<SearchBox />
<Dropdown />
</div>
</TabPanel>
{/*
Tabのタイトル
*/}
<div css={tabWrapper}>
<TabList css={tabList}>
<Tab>
<TabTitle
index={0}
title='プレスリリース'
count={totalPressReleasesCount}
/>
</Tab>
<Tab>
<TabTitle index={1} title='ストーリー' count={storiesCount} />
</Tab>
<Tab>
<TabTitle index={2} title='プレスキット' />
</Tab>
</TabList>
<RssShare />
</div>
{/*
各Tabのコンテンツ
*/}
<TabPanels>
<TabPanel css={tabContent}>
...省略(1番目Tabのコンテンツ)
</TabPanel>
<TabPanel css={tabContent}>
...省略(2番目Tabのコンテンツ)
</TabPanel>
<TabPanel css={tabContent}>
...省略(3番目Tabのコンテンツ)
</TabPanel>
</TabPanels>
</Tabs>
);
}
ListMenu
ListMenu
もTab
と同じで composable
コンポーネントです。しかし、今回はアクセシビリティのポイントを注目して欲しいです。
こちらのWAI-ARIAのガイドラインに従って、Reach UIのListMenuが実装されています。そのため、実装したDropdown
コンポーネントもスクリーンリーダーやキーボードでの操作にきちんと対応するので、アクセシビリティが良いコンポーネントになります。また、以下の画像のように、下方向と上方向のDropdown
が存在しますが、ーつのDropdown
です。画面の下のスペースがDropdown
のコンテンツの高さより小さい場合、Dropdown
のコンテンツが上方向に展開するようになり、UX視点からでも良い挙動だと考えられます。


import {
ListboxButton,
ListboxInput,
ListboxList,
ListboxOption,
ListboxPopover,
} from '@reach/listbox';
import { positionRight } from '@reach/popover';
import '@reach/listbox/styles.css';
import { AnimatePresence, motion } from 'framer-motion';
// Framer Motionにwrap
const MotionListboxPopover = motion(ListboxPopover, {
forwardMotionProps: true,
});
export const DropdownMenu = () => {
const onDropboxChange = (value: string) => {
// データ取得など〜
};
return (
<ListboxInput
value={value}
onChange={onDropboxChange}
>
{({ isExpanded }) => (
<div>
<ListboxButton
css={[
listboxButton,
variant === 'PC' ? listboxButtonPC : listboxButtonSP,
]}
arrow={<ArrowIcon variant='gray' direction='down' />}
>
{title}
</ListboxButton>
<AnimatePresence>
<MotionListboxPopover
key='listboxPopover'
data-testid='dropdown-popover'
position={positionRight}
css={[popOver, variant === 'PC' ? popOverPC : null]}
initial='collapsed'
animate={isExpanded ? 'open' : 'collapsed'}
exit='collapsed'
variants={popoverAnimation}
>
{isExpanded && (
<ListboxList>
{options.map((option) => (
<ListboxOption
key={option.key}
css={[
listboxOption,
isSelected(option) ? selected : null,
]}
value={option.value}
label={option.key}
>
<div css={optionWrapper}>
<DropdownCheckIcon
data-testid='dropdown-check-icon'
css={[
dropdownCheck,
isSelected(option)
? css`
visibility: visible;
`
: css`
visibility: hidden;
`,
]}
/>
<span>{option.key}</span>
</div>
</ListboxOption>
))}
</ListboxList>
)}
</MotionListboxPopover>
</AnimatePresence>
</div>
)}
</ListboxInput>
);
}
Reach UIのスタイリング
Reach UI
のコンポーネントはアクセシビリティ用以外のスタイルが入っていないと言いましたが、スタイルについての注意点を伝えたいと思います。
Reach UI
のStyle Guide通り、スタイリングの選択が2つあります。
- ベーススタイルを
include
して、必要な部分のスタイルを上書きする(こちらを推奨する) - ベーススタイル無しで
ベーススタイルあり、スタイルを上書き
以下の実装のように、webpackを利用する場合、'@reach/listbox/styles.css'
を直接にインポートして(styleの内容はこちらでみれます)、必要な部分のスタイルだけを上書きすることができます。直感で欠点だとみれるかもしれませんが、自分は良いトレードオフ
だと思います。なぜなら:
- ベーススタイルはコンポーネントの挙動の最低限スタイルだけが入っている
- 実際に上書きする部分は少なく、他のライブラリーを利用してもほぼ同じ部分を上書きしてスタイリングする必要がある(デザインシステムを実装する場合)
- 以下のようなアクセシビリティのところをきちんとカバーしてくれる(スタイルはとにかく、開発者にアクセシビリティのポイントを促す)
[data-reach-listbox-popover]:focus-within {
box-shadow: 0 0 4px Highlight;
outline: -webkit-focus-ring-color auto 4px;
}
以下はDropdown で上書きした部分の例です。詳細の上書き方法としては、Reach UIのスタイリングセクションにご参考頂けます。
// @reach/listbox/styles.css
[data-reach-listbox-option][data-current-nav] {
background: hsl(211, 81%, 46%);
color: hsl(0, 0%, 100%);
}
// Dropdownの中
const listboxOption = css`
&[data-current-nav] {
background: ${colors.white};
color: ${colors.text.primary};
}
`;
ベーススタイル無し
ベーススタイルをインクルードしなくても実装できますが、コンポーネントを正しく動作するために、ベーススタイルにあるスタイルを各自で実装する必要があります(全てのスタイルを実装する必要がある可能性が高い)。また、以下の赤い枠のように、Reach UIはベーススタイル無しよりも上書き方法を推奨していると示します。自分も同じで上書き方法を推奨します。

まとめ
Reach UI
の様々なメリットやデメリットを紹介しましたが、どのケースでReach UIを推奨するのが良いだと、以下のReach UI
を推奨するケースを考えましたので、ご参考になればと思います。
(Radix UIは新しく出てきたUIライブラリーでVercelなどの大手企業に利用されていて、自分も調査中なので、もし興味があれば調べてみるといいかもしれません)
