ReactのUIライブラリのRadix UIを利用するために知っておきたい基礎

Radix UI は React で利用することができるオープンソースの UI ライブラリです。UI ライブラリなのでダイアログやドロップダウンメニューをアプリケーションに実装したい場合に利用することで効率的に開発を進めることができます。
Radix UI は Themes(@radix-ui/themes), Primitives(@radix-ui/コンポーネント名), Icons(npm i @radix-ui/react-icons), Colors(@radix-ui/colors) の 4 つのパッケージに分かれており、ドキュメントも別々になっています。Redix Primitives はスタイルが全く適用されておらずスタイルは自分で行い機能だけを利用したい場合に利用するパッケージです。Themes は Primitives による機能の実装に加え利用するコンポーネントには事前にスタイルも含まれているパッケージです。Icons はアイコンの利用、Colors は事前に定義されているカラーパレットを利用したい場合に利用するパッケージです。
本文書では最初に Radix Primitives を利用して動作確認を行い、同じコンポーネントタグを利用して Radix Themes の動作確認を行います。動作確認を通して Primitives と Themes の違いも理解できるはずです。
Radix Primitives は本サイトでも紹介済みの Headless UI, Radix Themes は Chakra UI と同様の機能です。
目次
Radix Primitives
ダイアログやドロップダウンメニューなど機能だけを提供する Radix Primitives を利用した場合にどのように設定していくか簡単なコードを利用して確認します。スタイリングには Tailwind CSS を利用しますが Tailwind CSS の利用は必須ではありません。
環境の構築
Radix Primitives を利用するための環境の構築を行います。Vite を利用して React のプロジェクトを作成します。npm create vite@latest コマンドを実行するとプロジェクト名の設定と framework と variant の選択を行う必要があります。プロジェクト名には任意の名前をつけることができるので”radix-ui-react”, framework は”React”, variant には”TypeScript”を選択しています。
% npm create vite@latest
✔ Project name: … radix-ui-react
✔ Select a framework: › React
✔ Select a variant: › TypeScript
Scaffolding project in /Users/mac/Desktop/radix-ui-react...
Done. Now run:
cd radix-ui-react
npm install
npm run dev
プロジェクトの作成が完了したら、プロジェクトディレクトリに移動して npm install を実行します。
% cd radix-ui-react
% npm install
Tailwind CSS の設定
プロジェクトの作成後 Tailwind CSS のインストールと設定を行います。
% npm install -D tailwindcss postcss autoprefixer
設定ファイルを作成するために init コマンドを実行します。
% npx tailwindcss init -p
Created Tailwind CSS config file: tailwind.config.js
Created PostCSS config file: postcss.config.js
tailwind.config.js ファイルを開いて下記の設定を行います。
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
};
src フォルダの index.css を開いて tailwind のディレクティブを設定します。これまで記述されていた内容は上書きします。
@tailwind base;
@tailwind components;
@tailwind utilities;
インストール、設定が正常に完了しているか確認するために App.jsx ファイルを更新します。
function App() {
return <h1 className="text-3xl font-bold underline">Hello world!</h1>;
}
export default App;
表示される文字が太文字で文字にした線が表示されれば Tailwind CSS の初期設定は正常に行われています。

ドロップダウンメニューの設定
初期設定
環境の構築がが完了したら Radix UI を利用しない状態でのドロップダウンメニューを構成する要素を確認しておきます。Menu という名前のボタンをクリックすると Home, Service, About us, Contact の 4 つのメニューが表示されるものとします。
function App() {
return (
<div className="m-8">
<h1 className="text-xl font-bold">ドロップダウンメニュー</h1>
<div>
<button>Menu</button>
<ul>
<li>
<a href="/">Home</a>
</li>
<li>
<a href="/service">Service</a>
</li>
<li>
<a href="/aboutus">About us</a>
</li>
<li>
<a href="/contact">Contact</a>
</li>
</ul>
</div>
</div>
);
}
export default App;
ブラウザで確認すると以下のように表示されます。ドロップダウンメニューの機能は実装されていないので button 要素で作成されて Menu の文字列をクリックしても何も変化はありません。

Radix UI のドロップダウンメニューを利用するためにはパッケージのインストールを行う必要があります。@radix-ui の後には利用したいコンポーネントの名前を指定します。っドロップダウンメニューの場合は react-dropdown-menu となります。
% npm install @radix-ui/react-dropdown-menu
スタイルを設定していない場合
インストールしたコンポーネントを利用してドロップダウンメニューの設定を行なっていきますが、どのようなコンポーネントタグを利用できるかは Radix Primitives のドキュメントを確認する必要があります。
ドキュメントを参考にしてコードを書き換えると下記のようになります。
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
function App() {
return (
<div className="m-8">
<h1 className="text-xl font-bold">ドロップダウンメニュー</h1>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<button>Menu</button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item>
<a href="/">Home</a>
</DropdownMenu.Item>
<DropdownMenu.Item>
<a href="/service">Service</a>
</DropdownMenu.Item>
<DropdownMenu.Item>
<a href="/aboutus">About us</a>
</DropdownMenu.Item>
<DropdownMenu.Item>
<a href="/contact">Contact</a>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
);
}
利用しているタグを確認していきます。DropdownMenu.Root でドロップダウンメニュー全体をラップします。DropdownMenu.Trigger はドロップダウンメニューを開閉するためのボタンの設定を行います。DropdownMenu.Content はドロップダウンメニューが開いた時に表示されるコンテンツを設定します。DropdownMenu.Item はメニューとなるアイテムを設定します。DropdownMenu.Item を設定するためには DropdownMenu.Content は必須ですが DropdownMenu.Content の DropdownMenu.Item は必須ではありません。
ブラウザで確認すると”Menu”のみ表示されコンテンツの部分は非表示となっています。


Menu の文字列をクリックするとコンテンツが表示されます。

コンポーネントを利用するだけでスタイルは設定されていませんが開閉を行うドロップダウンメニューを実装することができました。ボタンだけではなくメニューを非表示した場合は”Esc”キーをクリックすることでメニューが閉じ、上下キーでメニューを移動することができます。またメニューのコンテンツの外側をクリックしてもメニューは閉じます。キーボードによるメニューの操作など自作で行おうとすると時間のかかる機能を簡単に実装することができます。
ブラウザ上にはエラーはありませんが、ブラウザのコンソールを確認すると Warning が表示されています。
Warning: validateDOMNesting(...): <button> cannot appear as a descendant of <button>.
button タグの子要素にさらに button 要素を設定することができないという警告なのでブラウザのデベロッパーツールで要素を確認すると DropdownMenu.Trigger タグが button タグとして設定されていることがわかります。
<button type="button" id="radix-:ra:" aria-haspopup="menu" aria-expanded="false" data-state="closed">
<button>Menu</button>
</button>
Warning をなくすためには DropdownMenu.Trigger タグに asChild props を設定するか Menu の文字列をラップしている button タグを削除する方法があります。asChild props を設定してみましょう。
<DropdownMenu.Trigger asChild>
<button>Menu</button>
</DropdownMenu.Trigger>
Warining は消え、再度デベロッパーツールで同じ場所の要素を確認すると button タグが 1 つになっていることが確認できます。
<button type="button" id="radix-:ra:" aria-haspopup="menu" aria-expanded="false" data-state="closed">Menu</button>;
DropdownMenu.Trigger がどのような props を持っているのかはドキュメントの API Reference で確認することができます。
ここまでの動作確認で Radix Primitives がどのようなものか理解が深まったかと思います。
スタイルの設定
ドロップダウンメニューの機能の実装はできたので次は Tailwind CSS を利用してスタイルを設定していきます。Radix Primitives を利用する利点の一つは各自が自由にデザインをカスタマイズできることです。
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
function App() {
return (
<div className="m-16">
<h1 className="text-xl font-bold mb-4">ドロップダウンメニュー</h1>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<button className="px-4 py-2 border rounded-lg bg-gray-600 hover:bg-gray-400 text-white font-bold outline-none">
Menu
</button>
</DropdownMenu.Trigger>
<DropdownMenu.Content className="border rounded-lg mt-2 min-w-[140px]">
<DropdownMenu.Item className="m-1 outline-none rounded-lg">
<a href="/" className="p-2 inline-block w-full">
Home
</a>
</DropdownMenu.Item>
<DropdownMenu.Item className="m-1 outline-none rounded-lg">
<a href="/service" className="p-2 inline-block w-full">
Service
</a>
</DropdownMenu.Item>
<DropdownMenu.Item className="m-1 outline-none rounded-lg">
<a href="/aboutus" className="p-2 inline-block w-full">
About us
</a>
</DropdownMenu.Item>
<DropdownMenu.Item className="m-1 outline-none rounded-lg">
<a href="/contact" className="p-2 inline-block w-full">
Contact
</a>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
);
}
export default App;
Tailwind CSS を設定後のドロップダウンメニューの開いていない状態です。スタイルを設定したことで文字列がボタンとして識別できるようになりました。

Menu ボタンをクリックするとボーダーで囲まれたコンテンツが表示されるようになりました。

コンテンツが Menu ボタンの中央に表示されていますが表示される場所を DropdownMenu.Content の align props で変更することができます。デフォルトでは”center”に設定されているので”start”を設定します。
<DropdownMenu.Content
className="border rounded-lg mt-2 min-w-[140px]"
align="start"
>
下記のように中央からボタンの左端に沿ってメニューが表示されるようになりました。Radix Primitives はスタイルに関する設定が全くないわけではありません。

メニューの間に区切りの線を入れたい場合には DropdownMenu.Separator タグを利用することができます。
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
function App() {
return (
<div className="m-16">
<h1 className="text-xl font-bold mb-4">ドロップダウンメニュー</h1>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<button className="px-4 py-2 border rounded-lg bg-gray-600 hover:bg-gray-400 text-white font-bold outline-none">
Menu
</button>
</DropdownMenu.Trigger>
<DropdownMenu.Content
className="border rounded-lg mt-2 min-w-[140px]"
align="start"
>
<DropdownMenu.Item className="m-1 outline-none rounded-lg">
<a href="/" className="p-2 inline-block w-full">
Home
</a>
</DropdownMenu.Item>
<DropdownMenu.Separator className="h-[1px] bg-gray-200 m-[5px]" />
<DropdownMenu.Item className="m-1 outline-none rounded-lg">
<a href="/service" className="p-2 inline-block w-full">
Service
</a>
</DropdownMenu.Item>
<DropdownMenu.Separator className="h-[1px] bg-gray-200 m-[5px]" />
<DropdownMenu.Item className="m-1 outline-none rounded-lg">
<a href="/aboutus" className="p-2 inline-block w-full">
About us
</a>
</DropdownMenu.Item>
<DropdownMenu.Separator className="h-[1px] bg-gray-200 m-[5px]" />
<DropdownMenu.Item className="m-1 outline-none rounded-lg">
<a href="/contact" className="p-2 inline-block w-full">
Contact
</a>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
);
}
export default App;
ブラウザで確認すると区切りの線が表示されます。

マウスオーバーしたメニューの背景色を設定したい場合には DropdownMenu.Item の data 属性を利用することができます。

各メニューの item に data-[highlighted]を設定しています。Tailwind CSS では data-[highlighted]:bg-gray-100 で設定することができます。
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
function App() {
return (
<div className="m-16">
<h1 className="text-xl font-bold mb-4">ドロップダウンメニュー</h1>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<button className="px-4 py-2 border rounded-lg bg-gray-600 hover:bg-gray-400 text-white font-bold outline-none">
Menu
</button>
</DropdownMenu.Trigger>
<DropdownMenu.Content
className="border rounded-lg mt-2 min-w-[140px]"
align="start"
>
<DropdownMenu.Item className="m-1 outline-none rounded-lg data-[highlighted]:bg-gray-100">
<a href="/" className="p-2 inline-block w-full">
Home
</a>
</DropdownMenu.Item>
<DropdownMenu.Separator className="h-[1px] bg-gray-200 m-[5px]" />
<DropdownMenu.Item className="m-1 outline-none rounded-lg data-[highlighted]:bg-gray-100">
<a href="/service" className="p-2 inline-block w-full">
Service
</a>
</DropdownMenu.Item>
<DropdownMenu.Separator className="h-[1px] bg-gray-200 m-[5px]" />
<DropdownMenu.Item className="m-1 outline-none rounded-lg data-[highlighted]:bg-gray-100">
<a href="/aboutus" className="p-2 inline-block w-full">
About us
</a>
</DropdownMenu.Item>
<DropdownMenu.Separator className="h-[1px] bg-gray-200 m-[5px]" />
<DropdownMenu.Item className="m-1 outline-none rounded-lg data-[highlighted]:bg-gray-100">
<a href="/contact" className="p-2 inline-block w-full">
Contact
</a>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
);
}
export default App;
設定後にブラウザを利用してメニューにマウスオーバーするとマウスオーバーしたメニューのみスタイルが反映されます。Service のメニューの上にマウスをオーバーしています。

このように Radix Primitives が持つ props や data 属性を利用しながらスタイルを設定してオリジナルデザインのドロップダウンメニューを作成することができます。
Portal
上記で設定したコンポーネント以外にもドロップダウンメニューで利用することができるコンポーネントが複数あります。その中の一つである Portal を設定します。
DropdownMenu.Content タグで DropdownMenu.Portal タグでラップします。
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<button className="px-4 py-2 border rounded-lg bg-gray-600 hover:bg-gray-400 text-white font-bold outline-none">
Menu
</button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="border rounded-lg mt-2 min-w-[140px]"
align="start"
>
//略
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
ブラウザ上での見た目に変化はありませんがコンテンツの表示される要素が Menu ボタンの直下ではなく body タグの閉じタグの前に表示されるようになります。

Portal の利用
DropdownMenu.Portal を利用しない時の表示場所も確認しておきます。

Radix Themes
Radix Primitives を利用したドロップダウンメニューの実装方法が理解できたので Radix Themes を利用した場合のドロップダウンメニューの設定方法について確認します。
環境の構築
Vite を利用して Radix Themes の動作確認を行うための React プロジェクトの作成を行います。npm create vite@latest コマンドを実行するとプロジェクト名の設定と framework と variant の選択を行う必要があります。プロジェクト名には任意の名前をつけることができるので”radix-theme-react”, framework は”React”, variant には”TypeScript”を選択しています。
% npm create vite@latest
✔ Project name: … radix-theme-react
✔ Select a framework: › React
✔ Select a variant: › TypeScript
Scaffolding project in /Users/mac/Desktop/radix-theme-react...
Done. Now run:
cd radix-theme-react
npm install
npm run dev
プロジェクトの作成が完了したら、プロジェクトディレクトリに移動して npm install を実行します。
% cd radix-theme-react
% npm install
Radix Themes のインストールと設定
プロジェクトの作成が完了したら、Radix Themes のインストールを行います。
% npm install @radix-ui/themes
インストール完了後 main.tsx ファイルで CSS ファイルの import と Theme コンポーネントの設定を行います。‘@radix-ui/themes/styles.css’を import, ‘@radix-ui/themes の Theme を import して App コンポーネントをラップします。
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import '@radix-ui/themes/styles.css';
import { Theme } from '@radix-ui/themes';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Theme>
<App />
</Theme>
</React.StrictMode>
);
Radix Themes の動作確認を行うために App.tsx ファイルに以下の設定を行います。
import { Flex, Text, Button } from '@radix-ui/themes';
export default function MyApp() {
return (
<Flex direction="column" gap="2">
<Text>Hello from Radix Themes :)</Text>
<Button>Let's go</Button>
</Flex>
);
}
ブラウザで確認すると CSS の class を利用することなくスタイルが設定されていることがわかります。

ドロップダウンメニューの設定
Radix Primitives の動作確認で”スタイルを設定していない場合”に利用したコードの button タグを Button コンポーネントに変更して設定します。‘@radix-ui/themes’からコンポーネントを import していますが利用してタグは同じです。
import { Button, DropdownMenu } from '@radix-ui/themes';
export default function MyApp() {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button>Menu</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item>
<a href="/">Home</a>
</DropdownMenu.Item>
<DropdownMenu.Item>
<a href="/service">Service</a>
</DropdownMenu.Item>
<DropdownMenu.Item>
<a href="/aboutus">About us</a>
</DropdownMenu.Item>
<DropdownMenu.Item>
<a href="/contact">Contact</a>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
);
}
ブラウザで確認するとスタイリングは何も設定していませんが Radix Theme では自動で設定されていることがわかります。

a タグのデフォルトのスタイルが反映されているので index.css ファイルでスタイルを無効化します。
a {
color: inherit;
text-decoration: none;
}
main.tsx ファイルで index.css ファイルを import します。
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import '@radix-ui/themes/styles.css';
import { Theme } from '@radix-ui/themes';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Theme>
<App />
</Theme>
</React.StrictMode>
);
再度ブラウザでドロップダウンメニューを確認します。Radix Themes で設定されているスタイリングが反映されることが確認できます。

Button のデザインは size, variant, color props などによって変更することができますがここでは variant でボタンの見た目を変更します。さらに DropdownMenu.Separator を追加します。
import { Button, DropdownMenu } from '@radix-ui/themes';
export default function MyApp() {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="soft">Menu</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item>
<a href="/">Home</a>
</DropdownMenu.Item>
<DropdownMenu.Item>
<a href="/service">Service</a>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item>
<a href="/aboutus">About us</a>
</DropdownMenu.Item>
<DropdownMenu.Item>
<a href="/contact">Contact</a>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
);
}
Menu ボタンの見た目が変わり、DropdownMenu.Separator タグを追加するだけで区切りの線が表示されます。

ボタンのカラーやマウスオーバーした際のメニューの背景の色を設定したい場合には color props を利用することができます。
import { Button, DropdownMenu } from '@radix-ui/themes';
export default function MyApp() {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="soft" color="orange">
Menu
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content variant="soft" color="orange">
<DropdownMenu.Item>
<a href="/">Home</a>
</DropdownMenu.Item>
<DropdownMenu.Item>
<a href="/service">Service</a>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item>
<a href="/aboutus">About us</a>
</DropdownMenu.Item>
<DropdownMenu.Item>
<a href="/contact">Contact</a>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
);
}

Radix Themes でタグのみでスタイリングを自動設定されてますがカラーやサイズなどのカスタマイズは props を通して行うことができます。細かなスタイル設定を行う必要がないので Radix Primitives よりも高速にアプリケーションの開発が行えます。
Radix Icons
Radix Icons をインストールして Radix Themes で設定したドロップダウンメニューにアイコンを表示させてみましょう。
% npm i @radix-ui/react-icons
Button コンポーネントの中で import した CaretDownIcon コンポーネントを利用します。
import { CaretDownIcon } from '@radix-ui/react-icons';
import { Button, DropdownMenu } from '@radix-ui/themes';
//略
<Button variant="soft" color="orange">
Menu
<CaretDownIcon />
</Button>
Menu ボタンの中に下向きのアイコンが表示されます。

ここまでの動作確認で Radix UI の Radix Primitives, Radix Themes の違いとそれぞれのパッケージの基本的な使用方法を理解することができました。