ブログ

UI SDK vs Base UI vs Radix: shadcn 風コンポーネントにはどのヘッドレスライブラリを選ぶか?

UI SDK、Base UI、Radix を比較。対応範囲、スタイル、ライセンス、エージェント。

Preet Suthar · · 9分で読めます

  • React
  • UI SDK
  • Base UI
  • Radix

今週まで、shadcn/ui 風のコンポーネントライブラリの下にあるプリミティブ層を選ぶことは、Radix と Base UI の二択でした。2026 年 11 月 12 日、Vercel Labs が三つ目の選択肢である UI SDK を 0.1.0-beta.1 として公開しました。他の二つと同じくヘッドレスですが、異なる考え方を中心に作られていて、どちらにもないものを備えています。AI エージェントがコンポーネントを読み取って操作できるブリッジです。

比較の前にお断りを。HextaUI のコンポーネントは現在 Base UI で作られていて、私はその隣に別の UI SDK 版を加えようとしています。どちらも気に入る理由があるので公平を心がけました。以下の主張はすべて、各ライブラリのドキュメント、npm のメタデータ、または同じコンポーネントを三回作った検証用アプリに基づいています。

Radix から Base UI への移行を、shadcn/ui のコードにおけるすべての API の変更とともに詳しく知りたい場合は、まずshadcn/ui が Base UI に切り替わりましたを読んでください。この記事ではそれを繰り返しません。UI SDK を加えて、どれから始めるべきかを考えます。

結論

  • 動いている Radix のコードベースがあり、チームが慣れていて、Radix にないコンポーネントが必要ないなら、Radix を選びます。安定していて、React 専用で、MIT です。
  • 安定した API で今四半期に出荷したい新しい React デザインシステムなら、Base UI を選びます。新しい shadcn/ui プロジェクトのデフォルトで、Radix より多くのコンポーネントを揃え、エコシステムの大半が移りつつある属性モデルを使っています。
  • React 以外も必要な場合、Color Picker、Date Picker、Tree のようなコンポーネントを別のライブラリなしで使いたい場合、あるいは AI エージェントに構造化されたツールで UI を操作させたい場合は、UI SDK を選びます。ベータ版であることは受け入れてください。

記事の残りはその根拠です。

三つの概要

RadixBase UIUI SDK
パッケージradix-ui 1.8.1@base-ui/react 1.9.0@ui-sdk/react 0.1.0-beta.1
ライセンスMITMITApache-2.0
ステータス安定版安定版ベータ
フレームワークReactReactReact、Vue、Svelte、Solid、Web Components
開いた状態data-state="open"data-opendata-open
入場と退場data-state のキーフレームdata-starting-style, data-ending-styledata-starting-style, data-ending-style
パーツに data-slotいいえいいえはい
ポップアップのパーツPortal、ContentPortal、Positioner、Popupトップレイヤーのコンテンツ
エージェントブリッジいいえいいえはい、WebMCP にも対応
Radix、Base UI、UI SDK の概要(2026 年 11 月)

三つともスタイルを持たず、フォーカス、キーボード、ARIA を代わりに扱ってくれます。比較したのは 2026 年 11 月 13 日時点の npm の最新版です: radix-ui 1.8.1(2026 年 10 月 12 日公開)、@base-ui/react 1.9.0(2026 年 10 月 9 日)、@ui-sdk/react 0.1.0-beta.1(2026 年 11 月 12 日)。

三つの異なるモデル

Radix はコンポーネントごとに一つのプリミティブを提供します。Dialog パッケージには Root、Trigger、Portal、Overlay、Content、Title、Description、Close があり、それが扱う単位です。フォーカストラップや閉じる処理といった内部のビヘイビアは非公開です。radix-ui パッケージがそれらすべてをまとめています。

Base UI も同じ形で、一つのコンポーネントに複数のパーツがあります。その周りに useRender や mergeProps といった公開ユーティリティを増やしていて、組み込みのものと同じように振る舞う独自のパーツを書けます。React 専用で、Radix、Material UI、Floating UI に携わった人たちが作っています。

UI SDK は層の分け方が違います。@ui-sdk/core はビヘイビアの TypeScript ライブラリで、ロービングフォーカス、タイプアヘッド、ポップアップ、ドラッグ&ドロップ、読み上げなど多数を含みます。プリミティブは definePrimitive を通じてビヘイビアを状態と名前付きスロットと組み合わせます。@ui-sdk/react からインポートするコンポーネントは React アダプターでレンダリングされたプリミティブで、同じプリミティブがそれぞれのアダプターを通じて Vue、Svelte、Solid、Web Components でもレンダリングされます。ドキュメントはこれをエージェント向けに打ち出しています。テスト済みのビヘイビアを渡せば、エージェントはアイコンピッカーやカンバンボードのような、ライブラリにないコンポーネントを作れるというわけです。

実際的な違いはこうです。Radix と Base UI では、必要なのに用意されていないコンポーネントはゼロから自分で作るしかありません。UI SDK では、組み込みのコンポーネントと同じビヘイビアから作れて、エージェントブリッジにも参加させられます。

同じポップオーバー、三つの構造

Vite のアプリで、同じ Tailwind のクラスを使って一つのポップオーバーを三回作りました。違いのあるパーツだけに絞ったツリーがこちらです:

import { Popover as BasePopover } from "@base-ui/react/popover"
import {
  Popover,
  PopoverContent,
  PopoverTitle,
  PopoverTrigger,
} from "@ui-sdk/react/popover"
import { Popover as RadixPopover } from "radix-ui"

function RadixVersion() {
  return (
    <RadixPopover.Root>
      <RadixPopover.Trigger className={trigger}>Radix</RadixPopover.Trigger>
      <RadixPopover.Portal>
        <RadixPopover.Content
          align="start"
          sideOffset={8}
          className={`${panel} origin-(--radix-popover-content-transform-origin) data-[state=open]:animate-[popover-in_150ms_ease-out] data-[state=closed]:animate-[popover-out_100ms_ease-in]`}
        >
          <p className="font-medium">Share project</p>
        </RadixPopover.Content>
      </RadixPopover.Portal>
    </RadixPopover.Root>
  )
}

function BaseUIVersion() {
  return (
    <BasePopover.Root>
      <BasePopover.Trigger className={trigger}>Base UI</BasePopover.Trigger>
      <BasePopover.Portal>
        <BasePopover.Positioner align="start" sideOffset={8}>
          <BasePopover.Popup
            className={`${panel} origin-(--transform-origin) transition-[opacity,scale] duration-150 data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:scale-95 data-ending-style:opacity-0`}
          >
            <BasePopover.Title className="font-medium">
              Share project
            </BasePopover.Title>
          </BasePopover.Popup>
        </BasePopover.Positioner>
      </BasePopover.Portal>
    </BasePopover.Root>
  )
}

function UISDKVersion() {
  return (
    <Popover>
      <PopoverTrigger className={trigger}>UI SDK</PopoverTrigger>
      <PopoverContent
        align="start"
        sideOffset={8}
        className={`${panel} origin-(--transform-origin) transition-[opacity,scale] duration-150 data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:scale-95 data-ending-style:opacity-0`}
      >
        <PopoverTitle className="font-medium">Share project</PopoverTitle>
      </PopoverContent>
    </Popover>
  )
}

trigger と panel は共通のクラス文字列で、Radix 版は入場と退場のために小さな @keyframes ルールを二つ使っています。結果は三つとも同じ見た目です:

Radix、Base UI、UI SDK とラベルの付いたトリガーの下に、それぞれ Share project と表示された三つの開いたポップオーバーが並んでいる
Radix、Base UI、UI SDK で作った同じポップオーバーを検証用アプリからキャプチャしたもの。ピクセルは一致する。DOM とコードは一致しない。

Radix

  • Popover.Root
  • Popover.Trigger
  • Popover.Portal
  • Popover.Content

Base UI

  • Popover.Root
  • Popover.Trigger
  • Popover.Portal
  • Popover.Positioner
  • Popover.Popup

UI SDK

  • Popover
  • PopoverTrigger
  • PopoverContentその場でレンダリングし、トップレイヤーへ

ポップアップを移動・配置するためだけにあるパーツ

同じポップオーバー、三つのパーツツリー

違うのはコンテンツの置き場所です。私のテストでは、Radix と Base UI はどちらも Portal を通じて開いたコンテンツを <body> の末尾に移動しました。UI SDK は popover="manual" を付けてコンテンツを自身のコンポーネント内に残し、ブラウザがそれをトップレイヤーに昇格させました。これには次のような影響があります:

  • 重なり順の争いがない。 トップレイヤーのコンテンツは、sticky ヘッダーや transform された親の z-index がいくつであってもページの上に表示されます。
  • イベントはツリーを通じてバブリングする。 UI SDK のドキュメントは、ポップアップを囲むクリックハンドラーやリンクが、ポップアップ内のクリックも受け取ると警告しています。ポップアップはクリック可能なカードの外に置いてください。
  • ラッパーのパーツが少ない。 side、align、sideOffset などの配置 props は Positioner ではなくコンテンツに付けます。

Radix と Base UI は何年もポータルをうまく扱ってきたので、一つ目の点は修正というより単純化です。二つ目の点は知っておくべき新しい挙動です。

スタイリングのフック

三つとも状態をデータ属性に載せますが、名前は異なり、すべてのパーツに名前を付けるのは一つだけです。

フックRadixBase UIUI SDK
開いた状態data-state="open"data-opendata-open
パーツ名なしなしすべてのパーツに data-slot
変形の原点--radix-popover-content-transform-origin--transform-origin--transform-origin
アンカーの幅--radix-popover-trigger-width--anchor-width--anchor-width
`className` での状態いいえ状態の関数状態の関数

data-slot の行は見た目以上に重要です。Radix と Base UI は data-slot を出力しないので、shadcn/ui はラッパーファイルで独自に付けています。私の検証用アプリでは、Base UI のポップアップには data-open、data-side、data-align があり、data-slot はありませんでした。UI SDK は data-slot="popover-content" のように、すべてのパーツに自分で付け、ドキュメントでは props で上書きできない管理対象の属性として扱っています。これでグローバル CSS とエージェントは、各パーツの安定した名前を得られます。

Tailwind でスタイリングしているなら、ポップアップの属性名が共通なので、Base UI から UI SDK への移行はほぼラッパーの削除です。Radix からの移行では data-[state=open]: のバリアントと Radix 接頭辞の変数を置き換えることになります。UI SDK のスタイリングのチュートリアルでは、四つのコンポーネントを shadcn 風のトークンで扱っています。

アニメーション

Radix は data-state に対する CSS アニメーションでマウントとアンマウントをアニメーションさせます。ドキュメントによると、退場アニメーションの再生中はアンマウントを保留します。JavaScript のアニメーションライブラリ向けには、多くのパーツが forceMount を受け付け、子要素をいつマウントするかをライブラリに任せられます。

Base UI は data-starting-style と data-ending-style を加えているので、キーフレームだけでなく CSS トランジションも両方向で使えます。ポップアップはアニメーションが終わるまでマウントされたままで、その後削除されます。Portal に keepMounted があれば非表示になります。アニメーションのガイドでは Motion も扱っていて、opacity を含む Motion のアニメーションを検出すると書かれています。

UI SDK は同じ二つの属性を同じ意味で使います。コンテンツは退場のトランジションやアニメーションが終わるまでマウントされたままで、keepMounted は閉じたコンテンツをページに残し、onOpenChangeComplete は開閉が終わったときに実行されます。UI SDK のドキュメントには、アニメーションライブラリが閉じたコンテンツを DOM に残す必要があるときは keepMounted を使うという注記以外、Motion のガイドはまだありません。

CSS だけのアニメーションなら、Base UI と UI SDK は実質的に同じです。Motion を多用するインターフェースでは、Base UI には現時点で文書化されたパターンがあり、UI SDK にはありません。

コンポーネントの網羅範囲

ライブラリ間でコンポーネントを数えるのは大ざっぱになります。分け方も名前の付け方も違うからです。目安として:

  • Radix: radix-ui 1.8.1 に Accordion から Tooltip まで 30 のプリミティブパッケージ。加えて Slot、Portal、Visually Hidden などのユーティリティ。
  • Base UI: ドキュメントの索引に 37 のコンポーネント。Radix にない Combobox、Autocomplete、Number Field、OTP Field、Drawer、Toast、Preview Card を含みます。
  • UI SDK: 0.1.0-beta.1 に同梱されたドキュメントに 55 のコンポーネントと二つのフック。

Base UI のコンポーネントのほとんどには UI SDK に対応するものがあり、名前が違うこともあります。Number Field は Number Input、Preview Card は Hover Card、OTP Field は OTP Input です。UI SDK には他の二つにないコンポーネントもあり、Calendar、Date Picker、Date Input、Time Input、Color Picker、Rating、Tree、Command、Carousel、Resizable、Image Cropper、File Input、Token Input、Trigger Menu、Timeline、Waterfall などがあります。

数は品質ではありません。Radix と Base UI のコンポーネントには何年もの本番での利用実績があります。UI SDK のコンポーネントページはテスト済みのビヘイビアを一行ずつ挙げていて、それは良い兆候ですが、公開から一日というのは何年分ものバグ報告と同じではありません。

エージェントブリッジ

ここは UI SDK にしかない部分です。@ui-sdk/react/agent の createAgent() はページ上のすべての UI SDK コンポーネントに接続します。snapshot() はそれらをラベル、状態、利用可能なアクションとともに一覧にします。act() は各コンポーネントの API を通じてそのアクションを呼び出し、変更コールバックには details.source === "agent" が渡されます。registerTools() は同じツールを、WebMCP を通じてブラウザに組み込まれたエージェントに提供します。

Radix と Base UI にはこのような機能は組み込まれていません。エージェントは他のウェブページと同じように、スクリーンショット、DOM、アクセシビリティツリーを通じ、クリックや入力を行うブラウザツールで操作します。

Vercel のドキュメントには、プロジェクト設定のタスクでこの違いを測ったベンチマークがあります。その数字によると、Claude Code 経由の Sonnet で三回の実行の中央値を取ったところ、エージェントブリッジによってツール呼び出しは 16 回から 5 回に、コストは $0.073 から $0.032 に減りました。これは一つのタスクについての Vercel の数字で私のものではありませんが、方向性は納得できます。状態付きのコンポーネント一覧を読むほうが、ページを読むより安上がりです。権限とカスタムコンポーネントを含むブリッジの動くデモは、React アプリを AI エージェントが操作できるようにするで作りました。

エージェントが UI を使うことがロードマップにないなら、このセクションで何かを決める必要はありません。あるなら、この比較の他の要素はどれも及びません。

成熟度とライセンス

Radix と Base UI は安定した 1.x のライブラリです。API は semver に従って変わり、shadcn/ui はすべてのコンポーネントを両方向けに提供しています。

UI SDK は README とドキュメントで、ベータ版であり、一般提供までにフレームワーク、API、ドキュメント、挙動が変わる可能性があると述べています。このベータは Vercel の公開ベータ契約の対象です。さらに一部の API は experimental とされていて、カスタムプリミティブの agent キーや、履歴と元に戻すのヘルパーが含まれます。GitHub リポジトリの vercel-labs/ui は 2026 年 10 月に作られ、変更履歴の唯一のエントリーは 2026 年 11 月 12 日の「Introducing UI SDK」です。

ライセンスも違います。Radix と Base UI は MIT です。UI SDK は Apache-2.0 で、これも寛容なライセンスですが、明示的な特許許諾が加わります。再配布する場合、Apache-2.0 はライセンスと通知を残すよう求めているので、それが重要なら社内でライセンスを担当している人に確認してください。

フレームワーク

Radix と Base UI は React のライブラリです。UI SDK は @ui-sdk/react、@ui-sdk/vue、@ui-sdk/svelte、@ui-sdk/solid、@ui-sdk/web を同じバージョンで公開していて、すべてが一つのコアの上にあります。会社で React のアプリと Vue のアプリを運用している場合や、CMS 向けに Web Components のビルドが欲しい場合、一つのビヘイビアと属性名のセットですべてをカバーできるのは三つの中で UI SDK だけです。

UI SDK の React パッケージには React 19 と React DOM 19 以降、@types/react 19.2.10 以降が必要です。まだ React 18 なら、当面はそれで決まりです。

それぞれを選ぶべきとき

アプリが動いていて、チームが慣れていて、行き詰まっていないなら、Radix に留まります。ここに移行を強いるものはありません。移行するなら、まず Base UI へ。そのほうが小さな一歩で、覚えた属性モデルは UI SDK にもそのまま通用します。

今出荷する React コンポーネントライブラリやプロダクトの UI を作るなら、Base UI で始めます。安定していて、shadcn/ui のデフォルトで、ポップアップ、フォーム、アニメーションのパターンがよく文書化されています。

次のどれかに当てはまるなら、UI SDK で始めます:

  • 複数のフレームワークで一つのコンポーネントモデルを共有する必要がある。
  • 追加のコンポーネントが必要で、そうでなければ日付、色、ツリーのために別のライブラリを足すことになる。
  • エージェントが UI を操作することがプロダクトの目標で、権限と変更元の仕組みが組み込まれていてほしい。
  • プロトタイプ段階で、ベータ期間中の破壊的変更を吸収できる。

混在は可能。 UI SDK のコンポーネントはプロバイダーを必要としないので、Base UI のアプリの一画面で UI SDK の Color Picker を使えます。依存関係が二つ、属性名のセットも二つになるので、他では手に入らないコンポーネントに限ってください。

HextaUI がやっていること

HextaUI のコンポーネントは Base UI のままです。サイトにあるのはその版で、なくなることはありません。並行して、同じ見た目とテーマトークンを持つ別の UI SDK 版を、エージェントブリッジや追加のコンポーネントが欲しいプロジェクト向けに作っています。まだリリースしていません。リリースされたら、移行ではなく版を選ぶことになります。