Radix UIは、React向けのUIコンポーネント群です。スタイルを持たずアクセシビリティの挙動だけを提供する「Radix Primitives」と、デザイン済みの「Radix Themes」に分かれます。2026年9月時点のnpm最新版は、Primitivesの統合パッケージ radix-ui が1.6.7(2026年7月24日公開)、Themesの @radix-ui/themes が3.3.0(2026年1月31日公開)です。この記事では、2つの違いと選び方、現行の導入手順、全30コンポーネント、asChild とDialogの落とし穴、shadcn/ui・Base UIとの関係を、公式ドキュメントとローカルでの描画結果をもとに説明します。
まとめ:Radix UIの要点と選ぶ条件
- Radix UIはPrimitives(無スタイル)とThemes(スタイル付き)の2本立てです。ThemesはPrimitivesの上に作られています。
- Primitivesは
npm install radix-uiの1パッケージで入れるのが公式の推奨です。個別の@radix-ui/react-dialogなども引き続き公開されています。 - 公式ドキュメントのコンポーネントは30件です。Form・One-Time Password Field・Password Toggle FieldはPreview扱いです。
- Dialogの「Titleが無い」警告はreact-dialog 1.1.17(2026年6月15日)で削除されました。Titleを付け忘れても開発中に気づけません。
- shadcn/uiは2026年7月2日に新規プロジェクトの既定をBase UIへ変えましたが、Radixの非推奨化はしていません。動いているRadix製アプリを移す理由にはなりません。
Radix UIの構成と開発元
Radix UIはもともとModulzが開発していました。WorkOSは2022年6月1日にModulzの買収を発表しており、現在のリポジトリのライセンス表記は「MIT License, Copyright © 2022-present WorkOS」です。商用プロジェクトでも無料で使えます。
「Radix UI」は製品群の総称で、npmでは次のパッケージに分かれます。
| 製品 | npmパッケージ | 最新版 | 週間DL(2026-09-20〜26) |
|---|---|---|---|
| Primitives(統合) | radix-ui | 1.6.7 | 約1,607万 |
| Primitives(個別) | @radix-ui/react-dialog ほか | 1.1.23(Dialog) | 約8,409万(Dialog) |
| Themes | @radix-ui/themes | 3.3.0 | 約104万 |
個別パッケージは、直接導入したアプリだけでなく統合パッケージの依存関係としてもインストールされるため、DL数を利用者数として比較できません。shadcn/uiのnew-yorkスタイルは、2026年2月に統合パッケージへ移行しています。ほかにカラーパレットのRadix Colorsとアイコン集のRadix Iconsもありますが、UI部品ではありません。
Radix PrimitivesとRadix Themesの違いと選び方
Primitivesは、キーボード操作・フォーカス管理・ARIA属性だけを受け持ち、見た目はすべて利用者が書きます。Themesは、Primitivesを部品として使い、色・余白・角丸をprops(accentColor、radius など)で切り替える完成品のライブラリです。@radix-ui/themes 3.3.0は依存関係に radix-ui を含んでいます。
| 観点 | Primitives | Themes |
|---|---|---|
| 見た目 | なし(CSSを自分で書く) | あり(props で調整) |
| 同梱CSS | なし | styles.css 約794KiB(gzip 約85KiB) |
| スタイル手段 | Tailwind・CSS Modules 等なんでも | props とテーマトークンが前提 |
| 向く場面 | 自社デザインシステムの土台 | 管理画面・社内ツール・試作 |
選び方ははっきりしています。デザイナーが作ったデザインシステムを実装するならPrimitivesです。Themesはトークンとpropsで閉じた設計なので、Figmaのデザインに1pxずつ合わせる用途では上書きが増えます。公式のスタイリングガイドも、Themesの見た目から大きく離れたい場合は「Radix Themesが合っているか考え直す」よう書いています。逆に、デザイナーがいない社内ツールならThemesで画面を早く揃えられます。
Radix Primitivesの導入手順
radix-uiと@radix-ui/react-*の違い
公式のIntroductionは、radix-ui パッケージを入れて必要なプリミティブをimportする方法を推奨しています。バージョンの食い違いや重複を防げるためです。radix-ui 1.6.7は55個の @radix-ui/* パッケージを依存関係として束ねたもので、個別パッケージが廃止されたわけではありません。
npm install radix-ui@latest
1.6.3からはプリミティブごとのサブパスも用意され、import * as Popover from "radix-ui/popover"; のように書けます。公式は、バンドラーによってはこちらのほうが不要なコードを削りやすいと説明しています。
Dialogの最小構成とスタイルの当て方
各プリミティブは Root・Trigger・Content などの部品に分かれています。掲載TSXはTypeScript 5.9.3、React 19.3.0、radix-ui 1.6.7で、strictとskipLibCheckを有効にした型検査を通過しています。依存パッケージの型宣言は検査対象から除外しています。
"use client";
import { Dialog } from "radix-ui";
export function DeleteDialog({ onDelete }: { onDelete: () => void }) {
return (
<Dialog.Root>
<Dialog.Trigger className="btn">削除</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="overlay" />
<Dialog.Content className="content">
<Dialog.Title>記事を削除しますか?</Dialog.Title>
<Dialog.Description>この操作は取り消せません。</Dialog.Description>
<Dialog.Close className="btn">キャンセル</Dialog.Close>
<button className="btn danger" onClick={onDelete}>削除する</button>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
開閉などの状態は data-state="open" / "closed" 属性として出力されるので、CSSは属性セレクタで書きます。
.overlay { position: fixed; inset: 0; background: rgb(0 0 0 / .4); }
.content { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: min(90vw, 32rem); max-height: 85vh; overflow: auto; padding: 1.5rem; background: white; color: #111; }
.content[data-state="open"] { animation: fadeIn 150ms ease-out; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.btn[data-state="open"] { outline: 2px solid #3b82f6; }
Next.js App Routerでのuse client指定
DialogやTooltipなど部品パッケージの配布ファイルは、先頭に "use client" を持っています。それでも、onClick や useState を書くファイルは自分でClient Componentにする必要があるため、上の例のように先頭へ "use client" を付けます。なお radix-ui 1.6.6と1.6.7は「React Server Componentsとの互換性問題を起こした破壊的変更を戻した」リリースです。1.6.3〜1.6.5を固定しているプロジェクトは1.6.7へ上げてください。
Radix Themesの導入手順と注意点
Themesは、パッケージを入れ、CSSを読み込み、Theme でアプリ全体を包む3手順です。
npm install @radix-ui/themes
import "@radix-ui/themes/styles.css";
import { Theme } from "@radix-ui/themes";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ja">
<body>
<Theme accentColor="crimson" grayColor="sand" radius="large">
{children}
</Theme>
</body>
</html>
);
}
Theme は class="radix-themes" と data-accent-color・data-radius などの属性を持つdivとして描画され、各部品はこの属性を手がかりに色を決めます。そのため、自作のPortalで Theme の外に描画した要素には色が付きません。公式は、Portalの中身をもう一度 Theme で包む方法を案内しています。
もう1つの注意点はCSSの量です。styles.css は使う部品の数にかかわらず全部品分を含み、3.3.0では812,667バイトありました。自前のCSSとの優先順位を調整したい場合は、tokens.css・components.css・utilities.css に分けて読み込めます。Tailwind CSS v3の @tailwind base はボタンのリセットでThemesのボタン背景を消すことがあるため、公式はCSSレイヤーを分けるなどの回避策を挙げています。
公式サイトのThemesリリースノートは3.1.3で更新が止まっていて、3.2.0以降の変更はGitHubのCHANGELOGにしか載っていません。GitHubには未公開の3.4.0の項目もあるので、npmのlatestと突き合わせて読んでください。
Radix Primitivesのコンポーネント一覧
公式ドキュメントのComponents欄は30件、Utilities欄は5件です。
| 分類 | コンポーネント |
|---|---|
| オーバーレイ | Dialog・Alert Dialog・Popover・Hover Card・Tooltip・Toast |
| メニュー | Dropdown Menu・Context Menu・Menubar・Navigation Menu |
| フォーム | Checkbox・Radio Group・Switch・Select・Slider・Label・Toggle |
| フォーム(Preview) | Form・One-Time Password Field・Password Toggle Field |
| 構造・表示 | Accordion・Collapsible・Tabs・Toggle Group・Toolbar |
| その他 | Avatar・Aspect Ratio・Progress・Scroll Area・Separator |
| ユーティリティ | Accessible Icon・Direction Provider・Portal・Slot・Visually Hidden |
Preview扱いの3件のうち、One-Time Password FieldとPassword Toggle Fieldは、radix-ui 1.6.7では unstable_OneTimePasswordField・unstable_PasswordToggleField という名前でexportされます。名前に unstable_ が付いている間はAPIが変わる前提で使ってください。Combobox(入力しながら候補を選ぶ部品)と日付選択は一覧にありません。これらが必要なら、Comboboxを持つBase UIなど別のライブラリと組み合わせます。
asChildとSlotの仕様:実測で分かった落とし穴
asChild を付けると、Radixは既定の要素(Dialog.Triggerなら button)を描画せず、直下の子要素にpropsと挙動を渡します。内部で使われているのがSlotで、className は親と子の値が連結されます。radix-ui 1.6.7を react-dom/server で描画すると、次の4つの失敗が確認できました。
- propsを展開しない自作コンポーネント:
({children}) => <button>{children}</button>にasChildで渡すと、aria-haspopup・aria-expanded・data-stateがすべて消え、クリックしてもDialogは開きません。エラーは出ません。 - 子が2つ以上、または文字列だけ:
Primitive.button failed to slot onto its childrenというエラーで描画が止まります。 - asChildを付け忘れてbuttonを渡す:
<button>の中に<button>が入る不正なHTMLになります。 - aタグに渡す:
<a href="#x" type="button">のように、リンクにもtype="button"が付きます。ページ遷移させたいならTriggerではなく普通のリンクにします。
自作コンポーネントを渡すときは、受け取ったpropsを要素へ展開します。React 19では ref もpropsとして届くので forwardRef は不要です。
import type { ComponentProps } from "react";
import { Tooltip } from "radix-ui";
function IconButton(props: ComponentProps<"button">) {
const { className, ...rest } = props;
return <button {...rest} className={["icon-btn", className].filter(Boolean).join(" ")} />;
}
export function SaveButton() {
return (
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<IconButton aria-label="保存">保存</IconButton>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content sideOffset={4}>保存(Ctrl+S)</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
);
}
Tooltipは Tooltip.Provider の外に置くと Tooltip must be used within TooltipProvider というエラーになります。アプリのルートに1つ置いておくのが簡単です。
Dialogのアクセシビリティ:Titleの警告が消えた後の確認方法
jsdomでDialogを開くと、Radixは次の処理を自動で行いました。
- Content内の最初のフォーカス可能な要素へフォーカスを移す
- Dialog以外の要素に
aria-hidden="true"を付け、スクリーンリーダーから隠す bodyにpointer-events: noneを付け、背景をクリックできなくする- Titleに
aria-labelledby、Descriptionにaria-describedbyを自動で結び付ける
注意が要るのはTitleです。以前のreact-dialogは、Titleが無いと開発中に DialogContent requires a DialogTitle で始まるエラーをコンソールへ出していました。npmの配布ファイルを版ごとに比べると、この警告文は1.1.16まであり、1.1.17で消えています。react-dialogのCHANGELOGにも Removed dev-only warnings for dialogs when title and/or description is not rendered. とあります。現行版ではTitleが無くても何も表示されず、Dialogの aria-labelledby が欠けたまま動きます。
対策は2つです。見出しを画面に出したくないDialogでもTitleは削除せず、Visually Hiddenで包みます。Descriptionが要らない場合は、公式どおり aria-describedby={undefined} を渡します。
import { Dialog, VisuallyHidden } from "radix-ui";
<Dialog.Content aria-describedby={undefined}>
<VisuallyHidden.Root asChild>
<Dialog.Title>サイト内検索</Dialog.Title>
</VisuallyHidden.Root>
<label>検索語
<input type="search" />
</label>
</Dialog.Content>
警告に頼れなくなった以上、テストで getByRole("dialog", { name: "サイト内検索" }) のようにアクセシブルネームで要素を取得しておくと、Titleの付け忘れがテストの失敗として出ます。また、defaultOpen で最初から開いたDialogでも、Portalの中身はサーバー描画のHTMLに含まれませんでした。検索エンジンに読ませたい本文をDialogの中だけに置かないでください。
SwitchとCheckboxのフォーム送信値
SwitchとCheckboxは、ネイティブの input ではなく button として描画されます。<form> の中に置いたSwitchは次のHTMLになりました。
<button type="button" role="switch" aria-checked="true" data-state="checked" value="on">
<span data-state="checked"></span>
</button>
<input type="checkbox" aria-hidden="true" tabindex="-1" name="n" checked="" value="on"
style="position:absolute;pointer-events:none;opacity:0;margin:0;transform:translateX(-100%)"/>
フォーム送信に使われるのは、視覚的に隠されたcheckboxです。値を指定しなければ送信値は on で、オフのときはネイティブのcheckboxと同じく送信されません。name を付け忘れると値は送られないので、Server Actionsや FormData で受け取る場合は必ず name を渡します。Checkboxも同じ構造です。radix-ui 1.6.2ではフォームのリセット時にRadioGroup・Slider・Select・Switchの値が戻らない不具合が、1.6.3では「すべて選択」のような一括更新でclickイベントが親へ伝わる不具合が直っています。フォーム部品を使うなら1.6.3以降にしてください。
shadcn/ui・Base UIとの関係と2026年の選び方
shadcn/uiは2023年1月の公開時からRadixを土台にしてきました。2026年7月2日の変更履歴で、新規プロジェクトの既定をBase UIへ切り替えています。Base UIはRadix・Material UI・Floating UIの開発者が作っている無スタイルのライブラリで、@base-ui/react 1.8.0が2026年9月4日に出ています。
同じ変更履歴には Radix is not being deprecated. You do not need to migrate. とあり、Radix版も更新が続きます。新規でRadixを使うなら npx shadcn init -b radix を指定します。CIなどで shadcn init を非対話で実行しているなら、このフラグを付けないと次からBase UIで生成されます。
判断の基準は次のとおりです。
- 既存のRadix製アプリ:現在の機能要件を満たしているなら、既定ライブラリの変更だけを理由に移行する必要はありません。
asChildがBase UIではrenderpropになるなど合成APIが違い、置き換えは部品ごとの書き換えになります。 - 新規で、Comboboxや複数選択のSelectが必要:Base UIを選びます。Radixにはこれらがありません。
- 新規で、上の部品が要らない:どちらでも構いません。社内に
asChildの書き方が定着しているならRadixのほうが教育コストが低く済みます。
両者のAPIの違いはBase UI(React)とは?Radix UI・MUIとの違いと使い方【v1.8対応】で、shadcn/uiの導入手順はshadcn/uiとは?読み方・導入手順とBase UI既定化の変更点をReact開発者向けに解説で詳しく扱っています。
MUI・Chakra UI・Headless UIとの比較
| ライブラリ | 最新版(npm) | 見た目 | 主な用途 |
|---|---|---|---|
| Radix Primitives | radix-ui 1.6.7 | なし | デザインシステムの土台 |
| Radix Themes | 3.3.0 | あり | 素早い画面構築 |
| Headless UI | 2.2.10 | なし | 無スタイルのReact・Vue向けUI |
| Material UI | 9.4.0 | あり(Material Design) | 業務アプリ全般 |
| Chakra UI | 3.37.0 | あり | propsでのスタイル指定 |
無スタイル同士で比べると、Radixのほうが部品数が多く、Headless UIに無いMenubar・Navigation Menu・Toast・Sliderを持っています。逆にComboboxはHeadless UIにあってRadixにありません。Headless UIの使い方はHeadless UIとは?React/Vueでの使い方・Tailwind連携・主要ライブラリ比較にまとめています。デザイン済みライブラリとの比較なら、Material Designに沿うかどうかが分かれ目で、沿うならMaterial UI、沿わないならRadix ThemesかChakra UIです。詳しくはMaterial UIとは:v9系の導入からGrid・テーマ設定までコード付きで解説とChakra UIとは?v3の使い方・インストール手順とv2移行の注意点【2026年9月】を参照してください。
よくある質問
Radix UIは無料で商用利用できますか?
できます。PrimitivesもThemesもMITライセンスです。
radix-uiと@radix-ui/react-dialogのどちらを入れればよいですか?
新規なら統合パッケージの radix-ui です。公式がこちらを推奨しており、プリミティブ間のバージョンの食い違いを防げます。既存プロジェクトの個別パッケージは、そのまま使い続けても問題ありません。
Radix UIは開発が終わったのですか?
終わっていません。radix-ui は2026年6月6日の1.5.0から7月24日の1.6.7まで続けて更新され、次の1.7.0のリリース候補もnpmに出ています。shadcn/uiも2026年7月の変更履歴で、Radixは非推奨にしないと明記しています。
Radix UIはReact 19に対応していますか?
対応しています。radix-ui 1.6.7と @radix-ui/themes 3.3.0のpeerDependenciesは、React 16.8から19までを許容しています。
Radix UIとTailwind CSSは併用できますか?
Primitivesなら問題なく併用できます。className を渡し、data-[state=open]: のような属性バリアントで状態ごとのスタイルを書きます。Themesと併用する場合は、Tailwindのbaseスタイルがボタンの背景を消すことがあるため、CSSレイヤーを分けてください。