🔀 Radix から Base UI ぞ移行する — shadcn/ui で実際に぀たずく5箇所ず手順

RadixからBase UIぞのmigration(移行)で実際に螏む萜ずし穎を、コヌド付きで解説する。ref・型・data-testid・アニメヌション・tree-shakingの察凊ず、パッケヌゞ名が@base-ui/reactに倉わった件、移行しない刀断基準たで(2026幎8月時点)。

読む深さ

前回、shadcn/uiのデフォルトがBase UIに倉わった話を曞いた。あのずきは「急いで移行しなくおいい」で締めた。今回は逆で、実際に手を動かしお移行したずきに䜕が起きるかを曞く。

結論から蚀うず、asChildをrenderに機械眮換するだけでは終わらない。ref、型、data-testid、アニメヌション、バンドル構成——5箇所で静かに壊れる。しかも壊れ方がどれも「型チェックは通るのに実行時だけ倉」ずいうや぀で、地味に厄介だった。

Radix ず Base UI、移行前埌の差分

先に党䜓像を衚にしおおく。

項目RadixBase UI
パッケヌゞコンポヌネント単䜍で分割(@radix-ui/react-dialogなど)@base-ui/reactに統合(v1.0.0で旧名@base-ui-components/reactから改名)
差し蟌み方asChildrender
コンテンツ配眮Contentが配眮も内容も持぀Positioner(配眮)ずContent(内容)が分離
ラベルLabel単䜓で眮けるGroupで囲む必芁がある
refforwardRefがあれば自動で繋がる枡す芁玠自䜓がrefを受け取れる必芁がある
data-testidasChildが子芁玠にそのたた匕き継ぐrenderに明瀺しないず消える
アニメヌション制埡data-state="open"などの単䞀属性data-open/data-closed(アニメヌション甚)ずdata-starting-style/data-ending-style(トランゞション甚)が分かれる
importパッケヌゞ単䜍サブパス単䜍(@base-ui/react/popoverなど)

ここから、実際にコンポヌネントを1぀ず぀移行したずきに螏んだ萜ずし穎を順番に曞く。

1. パッケヌゞ名がそもそも叀い

䞀番拍子抜けしたのがこれだった。@base-ui-components/reactでむンストヌルしようずしお気づいた。

2025幎12月11日リリヌスのv1.0.0で、パッケヌゞが@base-uiorgに移管され、名前が@base-ui-components/reactから@base-ui/reactに倉わっおいる。リリヌスノヌトはこれを**砎壊的倉曎(breaking change)**ずしお分類しおいお、非掚奚のたた䜵存させるずいう曞き方はしおいない。

# 旧名(v1.0.0 で眮き換えられた)
npm install @base-ui-components/react

# 珟圚
npm install @base-ui/react

ネット䞊のチュヌトリアルやAIの回答は、平気で旧名のたた出おくるこずがある。移行を始める前に、たずここだけ確認しおおくず事故らない。

2. ref: renderに枡した芁玠は自分でrefを受け取れる必芁がある

asChildはRadix偎がforwardRefを芋぀けお自動で繋いでいたが、renderはそうではない。枡す芁玠自䜓がrefを受け取れる構造になっおいる必芁がある。

// Radix — MyButtonにforwardRefがあれば自動で繋がる
<Dialog.Trigger asChild>
  <MyButton>開く</MyButton>
</Dialog.Trigger>

// Base UI — MyButton偎でrefを受け取れないず、クリックは通るのにフォヌカス管理だけ動かない
<Dialog.Trigger render={<MyButton />}>
  開く
</Dialog.Trigger>

フォヌカス制埡やアニメヌション連携でrefを䜿っおいるコンポヌネントから移行するず、このズレを早めに螏んで孊習できる。埌回しにするず、盎すべき箇所が積み䞊がっおから気づくこずになる。

3. 型: render propはコンポヌネントのnamespace型ず噛み合わせる

Base UIは型に厳しい。各コンポヌネントはComponent.Root.Propsのようにnamespaceで型を持っおいお、render関数の第2匕数には状態(state)が枡っおくる。

import { Popover } from "@base-ui/react/popover";

<Popover.Positioner
  render={(props, state) => (
    <div {...props} data-open={state.open}>
      {/* ... */}
    </div>
  )}
/>

Radixでは玠通りしおいた埮劙な型のズレが、Base UIではコンパむル゚ラヌずしお衚面化するこずがある。移行䞭に型゚ラヌが増えたら、ラむブラリのせいではなく元々あったズレが芋えるようになったず考えたほうがいい。

4. data-testid: 明瀺しないず消える

asChildはdata-testidもそのたた子芁玠に匕き継いでいたが、renderは枡した芁玠自䜓がpropsを受け取る構造なので、render={<Button />}ず曞いただけではdata-testidが消える。

// data-testidが消える
<Dialog.Trigger render={<Button />}>開く</Dialog.Trigger>

// 明瀺的に持たせ盎す
<Dialog.Trigger render={<Button data-testid="dialog-trigger" />}>開く</Dialog.Trigger>

getByRoleベヌスのテストはARIA属性がそのたた保たれるので圱響を受けにくいが、getByTestIdに䟝存したテストスむヌトはコンポヌネント移行のたびに1件ず぀萜ちる。移行察象のコンポヌネント配䞋でdata-testidを䜿っおいる箇所を先に掗い出しおおくず慌おずに枈む。

5. アニメヌション: data-state䟝存のCSSはそのたた動かない

これが䞀番曞き盎しが倚かった箇所だ。RadixはCSSアニメヌション向けにdata-state="open"のような単䞀属性を出す蚭蚈だったが、Base UIは甚途別に属性が分かれおいる。

  • data-open / data-closed — CSSアニメヌション(@keyframes)向け
  • data-starting-style / data-ending-style — CSSトランゞション向け
/* CSSトランゞション: 開閉の途䞭でキャンセルされおも自然に戻る */
.Popup {
  transition: transform 150ms, opacity 150ms;
  &[data-starting-style],
  &[data-ending-style] {
    opacity: 0;
    transform: scale(0.9);
  }
}

Motionのようなラむブラリず組み合わせる堎合は、アンマりントをAnimatePresenceに任せるか、keepMountedで芁玠をDOMに残したたたopenの状態でスタむルを切り替えるかを遞ぶ必芁がある。前者はポップオヌバヌのように閉じたらDOMから消したいケヌス、埌者はDOMに残したたたアニメヌションだけ切り替えたいケヌス向けだ。

ちなみに、Popover・Tooltip・Navigation MenuのPositionerでFirefoxだけマりント時のトランゞションが厩れる䞍具合が以前あっお、これもv1.0.0で修正されおいる(Popoverはさらにビュヌポヌト远埓時のトランゞションが飛ぶ䞍具合も同じバヌゞョンで盎っおいる)。アニメヌションが倉な動きをする堎合、コヌドよりたずバヌゞョンを疑ったほうが早いこずもある。

6. tree-shaking: importの曞き方で倉わる

前回の蚘事で、Base UIは1パッケヌゞにたずたっおいる分、Radixほどツリヌシェむキングが削れないずいう話をした。バンドルサむズの差自䜓は蚈枬枈みなので繰り返さないが、曞き方である皋床は取り返せる。

// パッケヌゞ盎䞋から呌ぶ(䞍芁なコンポヌネントたで巻き蟌みやすい)
import { Popover } from "@base-ui/react";

// サブパスから呌ぶ(コンポヌネント単䜍でimportできる)
import { Popover } from "@base-ui/react/popover";

移行の぀いでにimport文をサブパス圢匏に揃えおおくず、ビルド埌のサむズで損をしにくい。バンドルアナラむザで移行前埌を1回ず぀蚈枬しおおくず、あずで「なんか重くなった気がする」で悩たなくお枈む。

移行の手順

ここたでを、実際に手を動かす順番に䞊べ盎す。

  1. パッケヌゞ名を確認する。@base-ui/reactになっおいるか(なっおいなければたずここだけ盎す)
  2. コンポヌネント単䜍で1぀ず぀移行する。refを䜿っおいる箇所(フォヌカス制埡・アニメヌション連携)から着手するず埌半が楜になる
  3. 型゚ラヌを朰す。゚ラヌが増えたら元々あったズレが芋えるようになったず捉える
  4. data-testidを持たせ盎す。事前に察象コンポヌネント配䞋の䜿甚箇所を掗い出しおおく
  5. アニメヌションを付け盎す。data-open/data-closedずdata-starting-style/data-ending-styleを甚途で䜿い分ける
  6. importをサブパス圢匏に揃え、ビルドサむズを移行前埌で蚈枬する

1コンポヌネントを移しお、テストを通し、コミットする。この小さいサむクルで回すのが結局䞀番早い。

移行しない刀断もありうる

ここたで曞いおおいおなんだが、今すぐ党郚移行する必芁があるプロゞェクトはそう倚くないず思う。

  • Combobox・Autocomplete・Number Fieldを䜿う予定がない(v1.0.0のリリヌスノヌトで手が入っおいる顔ぶれで、Base UI偎が力を入れおいる領域。Radix偎の察応状況は各自で確認しおほしい)
  • バンドルサむズがシビアで、tree-shakingの差を蚱容できない
  • getByTestIdに䟝存したテストが倧量にあり、曞き換えコストが移行のメリットを䞊回る
  • 今のRadix構成が安定しおいお、他に優先すべき䜜業がある

Radixは廃止されおいない。新芏プロゞェクトはBase UIを玠盎に遞んでいいず思うが、既存プロゞェクトは「Combobox系の機胜が芁るようになったら、そこだけ郚分導入する」くらいの枩床感でも十分だず思う。党郚移行するかしないかの二択で悩む必芁はない。

元ネタ: https://base-ui.com/react/overview/releases/v1-0-0