フロントエンドエンジニアの上垣です。
この記事では、Radix Primitives と Tailwind CSS を使って、React コンポーネントを作成する方法を紹介します。
前提
この記事で紹介するソースコードは、Radix Primitives を利用している UIコンポーネントの、shadcn/ui のソースコードを参考にしています。より実践的な利用方法を知りたい場合は、合わせてご確認ください。
また、つい先日 Radix から、 Radix Primitives をベースにした UIコンポーネントライブラリ、 Radix Themes がリリースされましたが、この記事では触れていません。
Radix Primitives とは?
Radix Primitives は、アクセシブルな React UI コンポーネントライブラリです。Chakra UI などのコンポーネントライブラリと大きく異なる点は、「コンポーネントがスタイルを持たない」ということです。
スタイルされていないコンポーネントが何の役に立つのか? と疑問に思うかもしれませんが、例えば、独自のデザインシステムに基づいたコンポーネントライブラリ開発の課題を考えると、その存在意義を理解しやすくなります。
コンポーネントライブラリ開発でよくある課題
ChakraUI などの UI コンポーネントライブラリを利用する場合
⇒ デザインシステムに合わせてコンポーネント・テーマをカスタマイズ・維持するのが大変
スクラッチで開発する場合
⇒ コンポーネントが備えるべき振る舞いや、基本的なアクセシビリティを実装するのが大変
この課題に対して Radix Primitives を使うことで、自由にスタイリング可能で、かつアクセシブルで堅牢なコンポーネントを、素早く作成することができます。
Radix Primitives + Tailwind で Dialog コンポーネントを作ってみる
Radix Primitives が提供している Dialog コンポーネントを拡張して、Tailwind CSS でスタイリングした DIalog を作成してみます。なお、Radix Primitives は Tailwind には依存していないので、CSS in JS や 通常の CSS と合わせて使うことも可能です。(Docs)
install
インストールは、コンポーネント単位で実行します。これにより、既存プロジェクトにも、コンポーネント単位で小さく導入することが可能です。
npm install @radix-ui/react-dialog
Dialog が提供するコンポーネント
Radix の Dialog が提供するコンポーネント一覧は、こちら で確認できます。
Dialog.Overlay を拡張する例
例として、Dialog の背後に設置する Overlay コンポーネントを拡張してみます。(Docs)
Radix を使ったコンポーネント作成は、Radix がエクスポートしているコンポーネントに対して、必要なスタイルを追加して、再エクスポートしていくのが基本となります。
ソースコード
// Dialog.tsx import * as RadixDialog from "@radix-ui/react-dialog"; import { forwardRef, ElementRef, ComponentPropsWithoutRef } from "react"; import { twMerge } from "tailwind-merge"; export const Overlay = forwardRef< ElementRef<typeof RadixDialog.Overlay>, ComponentPropsWithoutRef<typeof RadixDialog.Overlay> >(({ className, ...props }, ref) => ( <RadixDialog.Overlay ref={ref} className={twMerge("bg-black bg-opacity-50 fixed inset-0", className)} {...props} /> )); Overlay.displayName = "Overlay";
ポイント
- 独自の Overlay コンポーネントの実体は、スタイルを追加した RadixDialog.Overlay です。
- fowardRef で、Radix.Overlay に ref を渡しています。
- Radix.Overlay の props はそのまま公開したいので、forwardRef のエイリアスに
ComponentPropsWithoutRef<typeof RadixDialog.Overlay>
を指定しています。 - RadixDialog.Overlay コンポーネントの className props で、デフォルトスタイル(背景色、ポジション)を指定しています。さらに、className は外部から props で拡張できるようにしつつ、twMerge で衝突を解消しています。
Dialog コンポーネントを完成させる
Radix の Dialog 関連コンポーネントを拡張した Codesandbox は こちら です。
拡張した Dialog コンポーネントに、以下の振る舞いが実装されていることを確認できます。
- こちら で定義されたキーボード操作
- Dialog 内のフォーカストラップ
- Dialog を開いた時に、最初のフォーカス可能要素がフォーカスされ、 Dialog を閉じた時に、トリガーがフォーカスされる
- Dialog が開いているときに、背後の DOM に、
aria-hidden="true"
が指定される - Dialog の重なり制御
スクラッチで実装したら大変な振る舞いばかりですが、Radix Primitives の力を借りることでスピーディーに実装することができました。また、今回は触れていませんが、Dialog の表示/消失 にアニメーションを追加することもできます。(Docs)
また、今回はRadix と独自のコンポーネントを 1対1 で対応させていますが、Raidx が提供するコンポーネントを全て再エクスポートする必要はなく、用途に合わせて必要なコンポーネントのみエクスポートしたり、複数のRadixコンポーネントを合成したコンポーネントを作成することも可能です。(Docs)
まとめ
スクラッチで実装するのは大変なコンポーネントを、Radix Primitives を導入することで、スピーディーに実装する例を紹介しました。
Goodpatch の開発案件では、独自のデザインシステムに合わせてコンポーネントライブラリを開発するケースもよくあるのですが、もしプロジェクトに既存の UI ライブラリが適合しない場合は、Radix Primitives の導入も検討してみようと思っています。
なお、Tailwind に特化した、スタイルを持たないコンポーネントライブラリとしては、Headless UI も人気です。提供されるコンポーネントは Radix Primitives より少ないですが、Vue にも対応しているので、Vue を利用している場合はこちらをお勧めします。