Radix Primitives + Tailwind CSS でアクセシブルなコンポーネントを素早く作る

フロントエンドエンジニアの上垣です。

この記事では、Radix Primitives と Tailwind CSS を使って、React コンポーネントを作成する方法を紹介します。

www.radix-ui.com

前提

この記事で紹介するソースコードは、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 がエクスポートしているコンポーネントに対して、必要なスタイルを追加して、再エクスポートしていくのが基本となります。

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 を利用している場合はこちらをお勧めします。