shadcn/ui、Radix、Headless UI、daisyUI、Flowbite、MUIの主な違いはスタイリングです。RadixとHeadless UIはスタイルなし、daisyUIとFlowbiteはスタイル済みのTailwind、MUIはMaterial Design、shadcn/uiはスタイル付きのコードをプロジェクトにコピーします。同じ6つのコンポーネントを各ライブラリで書き比べ、選び方も紹介します。
この6つのライブラリはそれぞれ別の問題を解決するものなので、「どれが一番か」に唯一の答えはありません。2つ(Radix PrimitivesとHeadless UI)は、スタイルなしで動作とアクセシビリティを提供します。2つ(daisyUIとFlowbite)は、完成したTailwindの見た目を提供します。MUIは独自のスタイリングエンジンを備えた完全なデザインシステムです。shadcn/uiはそもそもパッケージではありません。スタイル付きのコンポーネントのソースをプロジェクトにコピーするもので、その下にはヘッドレスの層があります。
以下の事実(バージョン、ライセンス、動作要件)はすべて、2026年10月に各プロジェクトの公式ドキュメント、GitHubリポジトリ、npmの掲載情報と照らし合わせて確認しました。バージョンはすぐに変わるので、数値は「執筆時点」のものとして扱ってください。
| shadcn/ui | Radix Primitives | Headless UI | daisyUI | Flowbite React | MUI (Material UI) | |
|---|---|---|---|---|---|---|
| インストールするもの | コンポーネントのソースをリポジトリにコピーするCLI | npmパッケージ(radix-ui、またはコンポーネントごとに1つ) | @headlessui/react | daisyui(Tailwindプラグイン) | flowbite-react | @mui/materialとEmotion |
| スタイリングの方式 | 自分が所有するコード内のTailwindクラス。テーマはCSS変数で | スタイルなし。クラスは自分で付け、状態はdata-state属性で | スタイルなし。状態はdata-*属性で、Tailwind向けに設計 | CSSのコンポーネントクラス(btn、modal)とテーマ | スタイル済みのTailwindコンポーネント、テーマ変更可能 | Material Design、CSS-in-JS(sxプロパティ、テーマオブジェクト) |
| バージョン(2026年10月) | CLIのshadcn 4.x(ランタイムのパッケージはなし) | radix-ui 1.8。個別パッケージは1.xと2.x | 2.2 | 5.x | 0.12(1.0未満) | 9.x |
| ライセンス | MIT | MIT | MIT | MIT | MIT | MIT(有料のMUI X Pro/Premiumは別扱い) |
| Reactの要件 | 必要 | 16.8〜19 | 18または19 | 不要(素のHTMLとCSS) | 18または19 | 17、18、19のいずれか |
| Tailwindの要件 | 必要(現在のドキュメントではv4) | 不要 | 不要だが、前提として設計 | 必要。daisyUI 5にはTailwind 4 | 必要。Tailwind 3または4 | 不要 |
| アクセシビリティへの取り組み | 土台のプリミティブ層(Base UIまたはRadix)から引き継ぐ | WAI-ARIA Authoring Practicesに準拠。キーボードとフォーカスの処理を内蔵 | 「完全にアクセシブル」とうたう。キーボードとフォーカスの処理を内蔵 | ネイティブ要素(<dialog>、<details>)に頼る。ARIAと動作は自分で追加 | Floating UIで位置決めするReactコンポーネント。キーボード操作は自分でテスト | コンポーネントごとにARIAとキーボード処理があり、それぞれ文書化されている |
| トーストの有無 | あり(Sonner経由) | あり(Toast) | なし | あり(レイアウト用クラスのみ) | あり(Toast) | あり(Snackbar) |
バンドルサイズは、意図的に表から外しました。その数値は、どのコンポーネントをインポートするか、バンドラーがツリーシェイキングするかどうか、そして(daisyUIの場合)使うクラスに対してTailwindがどれだけのCSSを生成するかに大きく左右されます。ライブラリごとに「kB」の数字を1つ示しても、役に立つより誤解を招くほうが大きいでしょう。自分のビルドで測ってください。実際に必要なコンポーネントを追加する前と後で、バンドラーの分析ツールを実行しましょう。
概要。shadcn/uiは自らを「コンポーネントライブラリではない」と言い、「コンポーネントライブラリを作るための方法」だと説明しています。CLIを実行すると(npx shadcn init、続いてnpx shadcn add button)、コンポーネントのソースがプロジェクトに書き込まれます。それ以降、コードは自由に編集でき、アップグレードすべきshadcnのランタイムパッケージはありません。
スタイリング。コピーされたファイル内のTailwind CSSクラスで、色や角の丸みはCSS変数になっているため、1か所でテーマを変えられます。バリエーションは通常class-variance-authorityで定義します。
内部の仕組み。操作できる部分は、ヘッドレスライブラリの上に作られています。shadcn/uiの変更履歴によれば、2026年7月以降、新しいプロジェクトの既定はBase UIで、Radixは「非推奨になるわけではない」とされ、両方がサポートされています。トーストは、別のオープンソースのトーストパッケージであるSonnerで作られています。
ライセンスとバージョン。MIT(GitHubリポジトリshadcn-ui/ui)。執筆時点で、CLIパッケージshadcnはnpm上で4.xでした。
注意点。コードを所有するということは、保守も自分で担うということです。元のプロジェクトでの修正は手元のコピーに自動では届かず、プロジェクトごとの差も時間とともに広がっていきます。
概要。スタイルのない、アクセシブルなReactコンポーネント群です(ダイアログ、ドロップダウンメニュー、タブ、スイッチ、トースト、ツールチップ、ポップオーバーなど)。単一のradix-uiパッケージ(執筆時点で1.8)からすべてをインストールすることも、@radix-ui/react-dialogのようにコンポーネントごとのパッケージを入れることもできます。
スタイリング。既定ではありません。各パーツは素の要素を描画し、どんな方法でもスタイルを付けられます。状態はdata-state="open"やdata-highlightedのようなデータ属性として公開され、Tailwindのdata-[state=open]:バリアントと相性が良いです。
アクセシビリティ。Radixのドキュメントによれば、プリミティブはWAI-ARIA Authoring Practicesに準拠し、キーボード操作に対応し、フォーカスを管理します(たとえば、開いたダイアログの中にフォーカスを閉じ込め、閉じたときに元へ戻します)。
ライセンスと動作要件。MIT。npmのピア依存関係の範囲は、React 16.8から19までです。
注意点。スタイルはすべて自分で書きます。Radixにはボタンのプリミティブがありません。ネイティブの<button>を使うか、Slotユーティリティでプロパティを自分の要素に統合しましょう。
概要。Tailwind CSSチームによる、スタイルのないコンポーネントです。Reactパッケージ@headlessui/react(執筆時点で2.2)には、Button、Checkbox、Combobox、Dialog、Disclosure、Fieldset、Input、Listbox、Menu(ドロップダウン)、Popover、Radio Group、Select、Switch、Tabs、Textarea、Transitionが含まれます。
スタイリング。スタイルなしです。コンポーネントはデータ属性(data-open、data-checked、data-selected、data-focus)で状態を公開するので、data-checked:bg-blue-600(Tailwind 4の構文)やdata-[checked]:bg-blue-600(Tailwind 3)のようなTailwindのバリアントでスタイルを付けます。
アクセシビリティ。公式サイトではコンポーネントを「完全にスタイルなしで、完全にアクセシブル」と説明しています。キーボード操作、ダイアログ内でのフォーカスの閉じ込め、ARIAの紐付けが組み込まれています。
ライセンスと動作要件。MIT。React 18または19(npmのピア依存関係)。
注意点。コンポーネントの数はRadixより少なく、トースト、ツールチップ、スライダーがないため、それらには別のパッケージが必要です。
概要。btn、modal、dropdown、tabs、toggle、toastといったコンポーネントのクラス名と、一連のカラーテーマを追加するTailwind CSSプラグインです。提供するのはReactコンポーネントではなくCSSなので、どのフレームワークでも、フレームワークなしでも動きます。
スタイリング。修飾子付きの意味のあるクラス(btn btn-primary btn-sm)で、通常のTailwindユーティリティとも組み合わせられます。テーマはCSS変数で切り替わります。
動作要件。daisyUI 5(2025年2月に初リリース、執筆時点で5.x)はTailwind CSS 4を対象としており、アップグレードガイドでは最初にTailwindを更新するよう案内されています。インストールは、CSSに@plugin "daisyui";の1行を書くだけです。
アクセシビリティ。CSSだけなので、動作はネイティブのHTMLに由来します。モーダルは<dialog>要素を使い(Escapeで閉じ、フォーカスはブラウザーが処理します)、ドロップダウンは<details>かポップオーバーAPIを使います。それ以上のこと(タブのARIA、トーストの読み上げ)は自分で行う必要があります。
ライセンス。MIT。
注意点。Reactでは状態を自分でつなぎ込みます(たとえば、ref経由でshowModal()を呼ぶ)。自分で追加しない限り、メニューやタブで矢印キーを使った操作はできません。
概要。Flowbiteプロジェクトによる、Tailwindでスタイリングされたコンポーネント(flowbite-react)です。Button、Modal、Dropdown、Tabs、ToggleSwitch、Toast、Navbar、Sidebar、Datepickerなどが含まれます。
スタイリング。Tailwindのクラスでスタイル済みで、テーマオブジェクトと各コンポーネントのthemeプロパティでカスタマイズします。
動作要件とバージョン。執筆時点でバージョン0.12です。1.0に達していないので、マイナーバージョン間でも互換性のない変更があると考えておきましょう。npmのピア依存関係は、React 18または19と、Tailwind CSS 3または4です。ドロップダウンとツールチップの位置決めにはFloating UIを使っています。
アクセシビリティ。よくあるパターンについては、コンポーネントが想定どおりのロールを描画しますが、Radixに匹敵するアクセシビリティに関する公式の説明は見つかりませんでした。頼りにするコンポーネントについては、キーボードとスクリーンリーダーでの動作をテストしてください。
ライセンス。MIT(オープンソースのライブラリの場合。Flowbiteは別途、Proブロックやデザインファイルも販売しています)。
概要。自らの言葉では「GoogleのMaterial Designを実装したオープンソースのReactコンポーネントライブラリ」です。この比較の中で最も大きなセットで、データ表示、入力、ナビゲーション、レイアウト、フィードバックのコンポーネントを含みます。
スタイリング。CSS-in-JSです。既定ではEmotionを使います(@emotion/reactと@emotion/styledは@mui/material 9のピア依存関係です)。テーマはcreateThemeで設定し、個別のスタイルはsxプロパティで付けます。Tailwindと併用もできますが、自分でつながない限り、2つのテーマの仕組みはトークンを共有しません。
バージョンとライセンス。バージョン9(9.0.0は2026年4月に公開)。中核のライブラリはMITです。MUI X ProとPremiumにある高度なデータグリッド、日付、チャートの機能は商用ライセンスです。
アクセシビリティ。コンポーネントにはARIA属性とキーボード処理が備わっており、MUIのドキュメントの各コンポーネントのページで、アクセシビリティ上の動作が個別に説明されています。
注意点。Materialの見た目から離れるには相当なテーマ作業が必要で、CSS-in-JSはReact Server Componentsと組み合わせる際に追加の設定が要ります。
以下では、同じボタン、ダイアログ、ドロップダウンメニュー、タブ、スイッチ、トーストを、各ライブラリの公式APIで1回ずつ、計6回書いています。どのコード例もこのページのために書いたオリジナルのコードで(各ライブラリのドキュメントからのコピーではありません)、各ライブラリの現在のメジャーバージョンを使い、違いのある部分だけに絞っています。実際にクリックできる版はRadix Primitivesのみで示しています。このサイトにインストールされているライブラリがRadixだからです。Radixにはスタイルがないので、私たち独自のTailwindクラスでスタイリングしています。ほかの5つのライブラリの動く版は示していないので、ここにそれらの見た目を模したものはありません。
ほとんどのライブラリには、バリエーション付きのボタンがあります。RadixとHeadless UIは見た目を任せている一方、daisyUIはバリエーションをクラスで、ほかのライブラリはプロパティで表している点に注目してください。
// after: npx shadcn add button
import { Button } from "@/components/ui/button";
<Button>Save changes</Button>
<Button variant="outline" size="sm">Cancel</Button>
<Button variant="destructive" disabled>Delete</Button>
<Button asChild><a href="/pricing">See pricing</a></Button>
// Radix has no Button primitive. Use a native button,
// or Slot to pass props onto your own element.
import { Slot } from "radix-ui";
function Button({ asChild, className, ...props }) {
const Comp = asChild ? Slot.Root : "button";
return <Comp className={"rounded-lg bg-black px-4 py-2 text-white " + className} {...props} />;
}
import { Button } from "@headlessui/react";
<Button className="rounded-lg bg-black px-4 py-2 text-white
data-hover:bg-black/80 data-active:bg-black/70
data-disabled:opacity-50">
Save changes
</Button>
<button className="btn btn-primary">Save changes</button>
<button className="btn btn-outline btn-sm">Cancel</button>
<button className="btn btn-error" disabled>Delete</button>
<a className="btn btn-link" href="/pricing">See pricing</a>
import { Button } from "flowbite-react";
<Button>Save changes</Button>
<Button color="alternative" size="sm">Cancel</Button>
<Button color="red" disabled>Delete</Button>
<Button href="/pricing">See pricing</Button>
import Button from "@mui/material/Button";
<Button variant="contained">Save changes</Button>
<Button variant="outlined" size="small">Cancel</Button>
<Button variant="contained" color="error" disabled>Delete</Button>
<Button href="/pricing">See pricing</Button>
ダイアログには、アクセシビリティの作業が隠れています。フォーカスの閉じ込め、Escapeで閉じる、開いたボタンへのフォーカスの返却、ラベル付けです。ヘッドレスのライブラリとMUIはこれをJavaScriptで行います。daisyUIはネイティブの<dialog>要素に頼り、Escapeとフォーカスはブラウザーが処理します。
import {
Dialog, DialogContent, DialogDescription, DialogFooter,
DialogHeader, DialogTitle, DialogTrigger, DialogClose,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
<Dialog>
<DialogTrigger asChild><Button>Edit profile</Button></DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>Update your display name.</DialogDescription>
</DialogHeader>
{/* form fields */}
<DialogFooter>
<DialogClose asChild><Button variant="outline">Cancel</Button></DialogClose>
<Button type="submit">Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>
import { Dialog } from "radix-ui";
<Dialog.Root>
<Dialog.Trigger className="btn-dark">Edit profile</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/40" />
<Dialog.Content className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-xl bg-white p-5">
<Dialog.Title>Edit profile</Dialog.Title>
<Dialog.Description>Update your display name.</Dialog.Description>
{/* form fields */}
<Dialog.Close>Cancel</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
import { Dialog, DialogPanel, DialogTitle, DialogBackdrop } from "@headlessui/react";
import { useState } from "react";
const [open, setOpen] = useState(false);
<button onClick={() => setOpen(true)}>Edit profile</button>
<Dialog open={open} onClose={setOpen} className="relative z-50">
<DialogBackdrop className="fixed inset-0 bg-black/40" />
<div className="fixed inset-0 flex items-center justify-center p-4">
<DialogPanel className="max-w-md rounded-xl bg-white p-5">
<DialogTitle className="font-semibold">Edit profile</DialogTitle>
{/* form fields */}
<button onClick={() => setOpen(false)}>Cancel</button>
</DialogPanel>
</div>
</Dialog>
import { useRef } from "react";
const ref = useRef<HTMLDialogElement>(null);
<button className="btn" onClick={() => ref.current?.showModal()}>Edit profile</button>
<dialog ref={ref} className="modal">
<div className="modal-box">
<h3 className="text-lg font-bold">Edit profile</h3>
{/* form fields */}
<div className="modal-action">
<form method="dialog"><button className="btn">Cancel</button></form>
</div>
</div>
<form method="dialog" className="modal-backdrop"><button>close</button></form>
</dialog>
import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from "flowbite-react";
import { useState } from "react";
const [open, setOpen] = useState(false);
<Button onClick={() => setOpen(true)}>Edit profile</Button>
<Modal show={open} onClose={() => setOpen(false)} size="md">
<ModalHeader>Edit profile</ModalHeader>
<ModalBody>{/* form fields */}</ModalBody>
<ModalFooter>
<Button color="alternative" onClick={() => setOpen(false)}>Cancel</Button>
<Button onClick={() => setOpen(false)}>Save</Button>
</ModalFooter>
</Modal>
import Button from "@mui/material/Button";
import Dialog from "@mui/material/Dialog";
import DialogTitle from "@mui/material/DialogTitle";
import DialogContent from "@mui/material/DialogContent";
import DialogActions from "@mui/material/DialogActions";
import { useState } from "react";
const [open, setOpen] = useState(false);
<Button variant="contained" onClick={() => setOpen(true)}>Edit profile</Button>
<Dialog open={open} onClose={() => setOpen(false)}>
<DialogTitle>Edit profile</DialogTitle>
<DialogContent>{/* form fields */}</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="contained" onClick={() => setOpen(false)}>Save</Button>
</DialogActions>
</Dialog>
メニューには、矢印キーでの移動、先頭文字での検索、正しいロールが必要です。Radix、Headless UI、shadcn/ui、MUIはこれに対応しています。daisyUIの<details>版はJavaScriptなしで開閉しますが、矢印キーでの移動は加わりません。
import {
DropdownMenu, DropdownMenuContent, DropdownMenuItem,
DropdownMenuSeparator, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
<DropdownMenu>
<DropdownMenuTrigger asChild><Button variant="outline">Options</Button></DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem onSelect={rename}>Rename</DropdownMenuItem>
<DropdownMenuItem onSelect={duplicate}>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
import { DropdownMenu } from "radix-ui";
<DropdownMenu.Root>
<DropdownMenu.Trigger>Options</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content sideOffset={6} className="rounded-lg bg-white p-1 shadow-lg">
<DropdownMenu.Item onSelect={rename} className="data-[highlighted]:bg-black/5">Rename</DropdownMenu.Item>
<DropdownMenu.Item onSelect={duplicate}>Duplicate</DropdownMenu.Item>
<DropdownMenu.Separator className="my-1 h-px bg-black/10" />
<DropdownMenu.Item disabled>Move</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
<Menu>
<MenuButton>Options</MenuButton>
<MenuItems anchor="bottom start" className="rounded-lg bg-white p-1 shadow-lg">
<MenuItem>
<button onClick={rename} className="block w-full data-focus:bg-black/5">Rename</button>
</MenuItem>
<MenuItem>
<button onClick={duplicate} className="block w-full data-focus:bg-black/5">Duplicate</button>
</MenuItem>
</MenuItems>
</Menu>
<details className="dropdown">
<summary className="btn">Options</summary>
<ul className="menu dropdown-content z-1 w-52 rounded-box bg-base-100 p-2 shadow-sm">
<li><button onClick={rename}>Rename</button></li>
<li><button onClick={duplicate}>Duplicate</button></li>
</ul>
</details>
import { Dropdown, DropdownDivider, DropdownItem } from "flowbite-react";
<Dropdown label="Options" dismissOnClick>
<DropdownItem onClick={rename}>Rename</DropdownItem>
<DropdownItem onClick={duplicate}>Duplicate</DropdownItem>
<DropdownDivider />
<DropdownItem>Delete</DropdownItem>
</Dropdown>
import Button from "@mui/material/Button";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import { useState } from "react";
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const close = () => setAnchorEl(null);
<Button onClick={(e) => setAnchorEl(e.currentTarget)}>Options</Button>
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={close}>
<MenuItem onClick={() => { rename(); close(); }}>Rename</MenuItem>
<MenuItem onClick={() => { duplicate(); close(); }}>Duplicate</MenuItem>
</Menu>
正しいタブには、role="tablist"、role="tab"、aria-selectedと、矢印キーによるタブ間の移動が必要です。daisyUIとMUIではパネルとIDを自分でつなぎ込みますが、ほかのライブラリは自動でつないでくれます。
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="billing">Billing</TabsTrigger>
</TabsList>
<TabsContent value="account">Account settings</TabsContent>
<TabsContent value="billing">Billing settings</TabsContent>
</Tabs>
import { Tabs } from "radix-ui";
<Tabs.Root defaultValue="account">
<Tabs.List aria-label="Settings">
<Tabs.Trigger value="account" className="data-[state=active]:font-semibold">Account</Tabs.Trigger>
<Tabs.Trigger value="billing" className="data-[state=active]:font-semibold">Billing</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="account">Account settings</Tabs.Content>
<Tabs.Content value="billing">Billing settings</Tabs.Content>
</Tabs.Root>
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react";
<TabGroup>
<TabList>
<Tab className="data-selected:font-semibold">Account</Tab>
<Tab className="data-selected:font-semibold">Billing</Tab>
</TabList>
<TabPanels>
<TabPanel>Account settings</TabPanel>
<TabPanel>Billing settings</TabPanel>
</TabPanels>
</TabGroup>
const [tab, setTab] = useState("account");
<div role="tablist" className="tabs tabs-box">
<button role="tab" aria-selected={tab === "account"}
className={"tab " + (tab === "account" ? "tab-active" : "")}
onClick={() => setTab("account")}>Account</button>
<button role="tab" aria-selected={tab === "billing"}
className={"tab " + (tab === "billing" ? "tab-active" : "")}
onClick={() => setTab("billing")}>Billing</button>
</div>
<div role="tabpanel">{tab === "account" ? "Account settings" : "Billing settings"}</div>
import { TabItem, Tabs } from "flowbite-react";
<Tabs aria-label="Settings" variant="underline" onActiveTabChange={(i) => console.log(i)}>
<TabItem active title="Account">Account settings</TabItem>
<TabItem title="Billing">Billing settings</TabItem>
</Tabs>
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import { useState } from "react";
const [tab, setTab] = useState(0);
<Tabs value={tab} onChange={(_, next) => setTab(next)} aria-label="Settings">
<Tab label="Account" id="tab-0" aria-controls="panel-0" />
<Tab label="Billing" id="tab-1" aria-controls="panel-1" />
</Tabs>
<div role="tabpanel" id={`panel-${tab}`} aria-labelledby={`tab-${tab}`}>
{tab === 0 ? "Account settings" : "Billing settings"}
</div>
スイッチは、role="switch"として読み上げられるオン・オフのコントロールです。daisyUIは本物のチェックボックスにスタイルを付けるので、ネイティブのフォームの動作が保たれます。ほかのライブラリは、スイッチのロールを持つボタンを描画します。
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
<div className="flex items-center gap-2">
<Switch id="email-alerts" checked={on} onCheckedChange={setOn} />
<Label htmlFor="email-alerts">Email notifications</Label>
</div>
import { Switch } from "radix-ui";
<Switch.Root id="email-alerts" checked={on} onCheckedChange={setOn}
className="h-6 w-11 rounded-full bg-black/20 data-[state=checked]:bg-blue-500">
<Switch.Thumb className="block h-5 w-5 rounded-full bg-white transition-transform data-[state=checked]:translate-x-5" />
</Switch.Root>
<label htmlFor="email-alerts">Email notifications</label>
import { Field, Label, Switch } from "@headlessui/react";
<Field className="flex items-center gap-2">
<Switch checked={on} onChange={setOn}
className="group h-6 w-11 rounded-full bg-black/20 data-checked:bg-blue-500">
<span className="block h-5 w-5 rounded-full bg-white transition group-data-checked:translate-x-5" />
</Switch>
<Label>Email notifications</Label>
</Field>
<label className="flex items-center gap-2">
<input type="checkbox" className="toggle toggle-primary"
checked={on} onChange={(e) => setOn(e.target.checked)} />
Email notifications
</label>
import { ToggleSwitch } from "flowbite-react";
<ToggleSwitch checked={on} onChange={setOn} label="Email notifications" />
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
<FormControlLabel
control={<Switch checked={on} onChange={(e) => setOn(e.target.checked)} />}
label="Email notifications"
/>
トーストは、ライブラリ間で最も違いが大きい部分です。状態で制御するコンポーネントを提供するものもあれば、どこからでも呼べる関数を提供するものもあり、Headless UIにはそもそもありません。
// after: npx shadcn add sonner
import { Toaster } from "@/components/ui/sonner";
import { toast } from "sonner";
// once, near the root of your app
<Toaster />
// anywhere
<Button onClick={() => toast("Changes saved", {
description: "Your profile is up to date.",
action: { label: "Undo", onClick: undo },
})}>
Save
</Button>
import { Toast } from "radix-ui";
<Toast.Provider duration={4000}>
<button onClick={() => setOpen(true)}>Save</button>
<Toast.Root open={open} onOpenChange={setOpen} className="rounded-lg bg-white p-3 shadow-lg">
<Toast.Title>Changes saved</Toast.Title>
<Toast.Description>Your profile is up to date.</Toast.Description>
<Toast.Action altText="Undo the save" onClick={undo}>Undo</Toast.Action>
</Toast.Root>
<Toast.Viewport className="fixed bottom-4 right-4 w-80" />
</Toast.Provider>
// Headless UI has no toast component. Common choices:
// pair it with a small toast package, or build one from
// Transition plus an aria-live region:
import { Transition } from "@headlessui/react";
<div aria-live="polite" className="fixed bottom-4 right-4">
<Transition show={open}>
<div className="rounded-lg bg-white p-3 shadow-lg transition data-closed:opacity-0">
Changes saved
</div>
</Transition>
</div>
{/* daisyUI positions and stacks; showing and hiding is your state */}
{open && (
<div className="toast toast-end toast-bottom" role="status">
<div className="alert alert-success">
<span>Changes saved</span>
</div>
</div>
)}
import { Toast, ToastToggle } from "flowbite-react";
{open && (
<Toast className="fixed bottom-4 right-4">
<div className="text-sm font-normal">Changes saved</div>
<ToastToggle onDismiss={() => setOpen(false)} />
</Toast>
)}
import Snackbar from "@mui/material/Snackbar";
import Button from "@mui/material/Button";
<Snackbar
open={open}
autoHideDuration={4000}
onClose={() => setOpen(false)}
message="Changes saved"
action={<Button size="small" onClick={undo}>Undo</Button>}
/>
| プロジェクト | 向いているもの | 理由 |
|---|---|---|
| Tailwindで作るマーケティングサイトやランディングページ | daisyUI、または素のTailwind | ほぼ静的なページに必要なのはボタン、カード、メニューで、JavaScriptのコンポーネント層ではありません。daisyUIのクラスはReact以外のページでも使えます。 |
| 独自のブランドを持つSaaSプロダクト | shadcn/ui、または自分でスタイリングしたRadix / Headless UI | ほかの製品の見た目を引き継がずにアクセシブルな動作が得られ、自分のデザインシステムに正確に合わせられます。 |
| 独自のデザインシステムやコンポーネントライブラリの構築 | Radix Primitives(またはBase UI)、Headless UI | スタイルのないプリミティブは、その上に自分のスタイルを載せたい層そのものです。shadcn/ui自体がこのやり方の一例です。 |
| 社内ツール、管理パネル、ダッシュボード | MUI、またはshadcn/ui | 見た目より早く公開することが大事な場合、MUIの幅広さ(表、MUI Xの日付選択、フォーム)が最も時間を節約します。すでにTailwindを使っているならshadcn/ui。 |
| Tailwindでのプロトタイプやハッカソン | Flowbite ReactまたはdaisyUI | ごくわずかなスタイリング作業で、完成度の高い見た目のコンポーネントが手に入ります。プロトタイプが製品になるなら、見直す前提でいましょう。 |
| すでにMaterial Design(またはAndroid風のUI)を使っているチーム | MUI | Material Designをそのまま実装しているので、デザイナーと開発者が同じ言葉で話せます。 |
| 厳しいアクセシビリティ要件(公共部門、大企業) | Radix Primitives、Headless UI、MUI、またはそれらの上に作ったshadcn/ui | キーボードとフォーカスの動作が組み込まれ、文書化されています。どれを選んでも、キーボードとスクリーンリーダーでのテストは欠かせません。ライブラリだけでページがアクセシブルになることはありません。 |
これらのコンポーネントの土台となるフックについては、Reactフックのチートシートをご覧ください。コード例全体で使っているユーティリティクラスはTailwindチートシートで、どのボタンにも必要なホバー、フォーカス、無効の状態はCSSボタンの状態で解説しています。コンポーネントギャラリーでは、ReactとTailwindで作った既製のモーダル、ボタン、ナビゲーションも閲覧できます。
2026年10月に確認:shadcn/uiのドキュメントと変更履歴(ui.shadcn.com)、shadcn-ui/uiのGitHubリポジトリ、アクセシビリティの概要を含むRadix Primitivesのドキュメント(radix-ui.com/primitives)、Headless UI(headlessui.com)、daisyUIのインストール、アップグレード、コンポーネントの各ページ(daisyui.com)、MUI Material UIのドキュメント(mui.com)、そしてバージョン、ライセンス、ピア依存関係については、shadcn、radix-ui、@headlessui/react、daisyui、flowbite-react、@mui/materialのnpmレジストリの掲載情報。
一般的な意味では違います。公式ドキュメント自身が、コンポーネントライブラリではなく、自分のライブラリを作るための方法だと説明しています。CLIがコンポーネントのソースをプロジェクトにコピーし、そのコードは自分で編集・保守します。操作できる部分はヘッドレスライブラリの上に作られており、2026年7月以降の新しいプロジェクトでは既定がBase UIで、Radixも引き続きサポートされています。
どちらもスタイルのない、アクセシブルなReactコンポーネントです。Radix Primitivesはトースト、ツールチップ、スライダー、ツールバーを含むより大きなセットで、React 16.8から19に対応しています。Headless UIはTailwind CSSチームによるもので、トーストやツールチップのない小さめのセットで、React 18または19が必要です。
使えます。daisyUIはbtn、modal、tabsのようなクラス名を追加するTailwind CSSプラグインなので、ほかのTailwindクラスと同じようにJSXの中でそのクラスを使います。Reactコンポーネントは提供しないため、モーダルを開く、タブを切り替えるといった状態は自分のReactコードで扱います。
はい。執筆時点(2026年10月)で、shadcn/ui、Radix Primitives、Headless UI、daisyUI、flowbite-react、そしてMUIの中核であるMaterial UIパッケージは、すべてMITライセンスです。MUI X ProとPremiumのコンポーネント、FlowbiteのProブロックは、別の商用製品です。
Radix Primitives、Headless UI、MUIは、キーボード操作とフォーカス管理をコンポーネントに組み込んでおり、shadcn/uiは土台のプリミティブ層からそれを引き継いでいます。daisyUIはネイティブのHTML要素に頼っているので、ARIAとキーボードの動作は自分で追加します。どれを使うにしても、キーボードとスクリーンリーダーでテストしましょう。
使えますが、スタイルの付け方が異なります。MUIはEmotionとJavaScriptのテーマを使い、Tailwindはユーティリティクラスを使います。両者は並べて使えますが、2つのテーマの間で色や余白の値を自分でそろえ続ける必要があります。すべてをTailwindで扱いたいチームは、たいていshadcn/uiかヘッドレスのライブラリを選びます。
ライブ:Radix Primitivesの{title}(スタイルなしのライブラリに、私たちのTailwindクラス)
プロフィールを編集
Tabを押すと、フォーカスは中に留まります。Escapeを押すか外側をクリックすると閉じます。
表示名
キャンセル
保存
閉じる
まだありません
オプション
名前を変更
複製
移動(無効)
隠しファイルを表示
矢印キーや文字入力での移動を試してください。最後に選んだ項目:{last}
設定
アカウント
支払い
チーム
アカウント設定のパネルです。タブでは左右の矢印キーを使ってください。
支払いのパネルです。
チームのパネルです。
メール通知:オン
メール通知:オフ
変更を保存
変更を保存しました
このデモで{n}回保存しました。
このデモで{n}回保存しました。
保存を取り消す
元に戻す
無料で始める · クレジットカード不要