{
  "title": "I migrated shadcn from Radix to Base UI (nobody asked me to)",
  "excerpt": "shadcn switched from Radix UI to Base UI by default in July 2026. Why I migrated all my projects over an entire weekend when nothing justified it — and whether you should do it too.",
  "locale": "en",
  "publishedAt": "2026-07-05T21:42:53.525Z",
  "updatedAt": "2026-07-05T21:42:53.571Z",
  "author": {
    "name": "Cédric TOURNIER",
    "username": "Amorem",
    "url": "https://mysaas.blog/en/@Amorem"
  },
  "tags": [],
  "relatedSaas": [],
  "sources": [
    {
      "title": "shadcn/ui — Base UI as the Default",
      "url": "https://ui.shadcn.com/docs/changelog/2026-07-base-ui-default",
      "hostname": "ui.shadcn.com"
    },
    {
      "title": "Base UI",
      "url": "https://base-ui.com/",
      "hostname": "base-ui.com"
    },
    {
      "title": "Migrating from Radix UI to Base UI",
      "url": "https://basecn.dev/docs/get-started/migrating-from-radix-ui",
      "hostname": "basecn.dev"
    }
  ],
  "urls": {
    "html": "https://mysaas.blog/en/@Amorem/i-migrated-shadcn-from-radix-to-base-ui-nobody-asked-me-to",
    "markdown": "https://mysaas.blog/en/@Amorem/i-migrated-shadcn-from-radix-to-base-ui-nobody-asked-me-to/md",
    "json": "https://mysaas.blog/en/@Amorem/i-migrated-shadcn-from-radix-to-base-ui-nobody-asked-me-to/json"
  },
  "wordCount": 538,
  "images": [
    {
      "url": "https://assets.mysaas.blog/posts/assets/2026-07-05/4dd1b313-9405-4f74-b4f4-afd43f1d39c5.png",
      "alt": null,
      "title": null,
      "width": null,
      "height": null
    },
    {
      "url": "https://assets.mysaas.blog/posts/assets/2026-07-05/4ded2ca3-4247-4509-9ba2-c111ae073372.png",
      "alt": "asChild to render prop diff",
      "title": "asChild → render",
      "width": 1200,
      "height": 440
    }
  ],
  "content": {
    "markdown": "This weekend, I moved shadcn from Radix UI to Base UI on pretty much all my projects. Nothing was breaking, everything worked, and the perf gain doesn't exist.\n\nIn short, a migration nobody recommends. I did it anyway. Here's why.\n\n## In 2023 it's Radix or nothing\n\nWhen shadcn/ui showed up in January 2023, it was built on Radix.\n\nHeadless components, accessible, clean APIs, battle-tested on millions of apps... shadcn didn't really *choose* Radix, it was just the only serious foundation around.\n\nYou copied unstyled primitives, slapped Tailwind on top, and it worked. For two years, no reason to even ask the question.\n\n## The same devs, but two years later\n\nThen the same people who built Radix (Colm Tuite chief among them), plus the guy behind Floating UI and the MUI team, went off to build Base UI.\n\nIt's not a competitor trying to kill Radix: it's the same team doing the job a second time, with two years of lessons in their pockets. Their v1 ships in late 2025, stable, with 35 components.\n\n## Base UI by default\n\nThe detail that changes everything: since July 2026, Base UI is the **default** in shadcn. You scaffold a new project, it comes up on Base UI, not Radix anymore.\n\n## What changes (spoiler: not perf)\n\nThe point is somewhere other than benchmarks. It's literal, you can see it in the code. Three examples.\n\nRadix's `asChild` becomes a `render` prop. Less magic hidden in a Slot, you see exactly what you're rendering:\n\n```\n// Radix\r\n\r\n  Ouvrir\r\n\r\n\r\n// Base UI\r\nOuvrir} />\n```\n\n![asChild to render prop diff](https://assets.mysaas.blog/posts/assets/2026-07-05/4ded2ca3-4247-4509-9ba2-c111ae073372.png)\n\nPopup positioning is no longer driven by props on `Content`, but by an explicit `Positioner` that wraps the `Popup`:\n\n```\n// Radix\r\n…\r\n\r\n// Base UI\r\n\r\n  …\r\n\n```\n\nAnd on the dependencies side, no more constellation of `@radix-ui/react-*`: a single native package.\n\n```\n# Radix\r\nnpm i @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-popover\r\n\r\n# Base UI\r\nnpm i @base-ui/react\n```\n\nMore verbose in places, sure. But more honest about what's actually happening. Bonus: Base UI ships primitives Radix never had — multiple Select, Combobox, Autocomplete, Drawer.\n\n## The real reason: the checkbox\n\nLet's be honest: everything above is after-the-fact rationalization.\n\nThe real reason is psychological. There's that category of people who, in a game, can't put the controller down until the bar hits 100%, every trophy unlocked, every box ticked. They're called completionists. I'm one of them.\n\nAnd \"recommended, available, stable, official default\" is exactly the kind of box I can't leave unticked. It's not tech debt I'm paying down, it's a 100% I'm unlocking.\n\n## Do we migrate or not?\n\nshadcn says it himself: the worst thing you can do to a production app is switch component libraries. He's right. You **don't** have to migrate — Radix is still mature, tested, supported, and nothing rots if you don't move.\n\nSo the reasonable advice:\n\n- stable app in production → don't touch a thing\n- living projects, templates, side-projects → migrate while it's painless and the API gap is still small\n- either way, there's an official migration skill (Claude Code, Cursor) that works component by component, one commit each, rollback = you delete the branch. Count on 20 minutes.\n\nAnyway, do whatever you want. But for me, the box is ticked.",
    "html": "<p>This weekend, I moved shadcn from Radix UI to Base UI on pretty much all my projects. Nothing was breaking, everything worked, and the perf gain doesn't exist.</p><p>In short, a migration nobody recommends. I did it anyway. Here's why.</p><h2>In 2023 it's Radix or nothing</h2><p>When shadcn/ui showed up in January 2023, it was built on Radix.</p><p>Headless components, accessible, clean APIs, battle-tested on millions of apps... shadcn didn't really <em>choose</em> Radix, it was just the only serious foundation around.</p><p>You copied unstyled primitives, slapped Tailwind on top, and it worked. For two years, no reason to even ask the question.</p><h2>The same devs, but two years later</h2><p>Then the same people who built Radix (Colm Tuite chief among them), plus the guy behind Floating UI and the MUI team, went off to build Base UI.</p><p>It's not a competitor trying to kill Radix: it's the same team doing the job a second time, with two years of lessons in their pockets. Their v1 ships in late 2025, stable, with 35 components.</p><h2>Base UI by default</h2><p>The detail that changes everything: since July 2026, Base UI is the <strong>default</strong> in shadcn. You scaffold a new project, it comes up on Base UI, not Radix anymore.</p><h2>What changes (spoiler: not perf)</h2><p>The point is somewhere other than benchmarks. It's literal, you can see it in the code. Three examples.</p><p>Radix's <code class=\"rounded-sm bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 font-mono text-sm\">asChild</code> becomes a <code class=\"rounded-sm bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 font-mono text-sm\">render</code> prop. Less magic hidden in a Slot, you see exactly what you're rendering:</p><pre><code>// Radix\r\n&lt;Dialog.Trigger asChild&gt;\r\n  &lt;Button&gt;Ouvrir&lt;/Button&gt;\r\n&lt;/Dialog.Trigger&gt;\r\n\r\n// Base UI\r\n&lt;Dialog.Trigger render={&lt;Button&gt;Ouvrir&lt;/Button&gt;} /&gt;</code></pre><img src=\"https://assets.mysaas.blog/posts/assets/2026-07-05/4ded2ca3-4247-4509-9ba2-c111ae073372.png\" alt=\"asChild to render prop diff\" title=\"asChild → render\" width=\"1200\" height=\"440\" data-image-size=\"medium\" data-media-id=\"cmr85erl5000hrlllqnupeemf\" data-media-sync-group=\"e1e1a506-93a3-45bb-84a4-37857ca9f381\" data-media-sync=\"linked\" /><p></p><p>Popup positioning is no longer driven by props on <code class=\"rounded-sm bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 font-mono text-sm\">Content</code>, but by an explicit <code class=\"rounded-sm bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 font-mono text-sm\">Positioner</code> that wraps the <code class=\"rounded-sm bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 font-mono text-sm\">Popup</code>:</p><pre><code>// Radix\r\n&lt;DropdownMenu.Content side=\"left\" align=\"start\"&gt;…&lt;/DropdownMenu.Content&gt;\r\n\r\n// Base UI\r\n&lt;DropdownMenu.Positioner side=\"left\" align=\"start\"&gt;\r\n  &lt;DropdownMenu.Popup&gt;…&lt;/DropdownMenu.Popup&gt;\r\n&lt;/DropdownMenu.Positioner&gt;</code></pre><p>And on the dependencies side, no more constellation of <code class=\"rounded-sm bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 font-mono text-sm\">@radix-ui/react-*</code>: a single native package.</p><pre><code># Radix\r\nnpm i @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-popover\r\n\r\n# Base UI\r\nnpm i @base-ui/react</code></pre><p>More verbose in places, sure. But more honest about what's actually happening. Bonus: Base UI ships primitives Radix never had — multiple Select, Combobox, Autocomplete, Drawer.</p><h2>The real reason: the checkbox</h2><p>Let's be honest: everything above is after-the-fact rationalization.</p><p>The real reason is psychological. There's that category of people who, in a game, can't put the controller down until the bar hits 100%, every trophy unlocked, every box ticked. They're called completionists. I'm one of them.</p><p>And \"recommended, available, stable, official default\" is exactly the kind of box I can't leave unticked. It's not tech debt I'm paying down, it's a 100% I'm unlocking.</p><h2>Do we migrate or not?</h2><p>shadcn says it himself: the worst thing you can do to a production app is switch component libraries. He's right. You <strong>don't</strong> have to migrate — Radix is still mature, tested, supported, and nothing rots if you don't move.</p><p>So the reasonable advice:</p><ul class=\"list-disc list-outside ml-6 space-y-1\"><li><p>stable app in production → don't touch a thing</p></li><li><p>living projects, templates, side-projects → migrate while it's painless and the API gap is still small</p></li><li><p>either way, there's an official migration skill (Claude Code, Cursor) that works component by component, one commit each, rollback = you delete the branch. Count on 20 minutes.</p></li></ul><p>Anyway, do whatever you want. But for me, the box is ticked.</p><p></p>",
    "text": "This weekend, I moved shadcn from Radix UI to Base UI on pretty much all my projects. Nothing was breaking, everything worked, and the perf gain doesn't exist. In short, a migration nobody recommends. I did it anyway. Here's why. In 2023 it's Radix or nothing When shadcn/ui showed up in January 2023, it was built on Radix. Headless components, accessible, clean APIs, battle-tested on millions of apps... shadcn didn't really choose Radix, it was just the only serious foundation around. You copied unstyled primitives, slapped Tailwind on top, and it worked. For two years, no reason to even ask the question. The same devs, but two years later Then the same people who built Radix (Colm Tuite chief among them), plus the guy behind Floating UI and the MUI team, went off to build Base UI. It's not a competitor trying to kill Radix: it's the same team doing the job a second time, with two years of lessons in their pockets. Their v1 ships in late 2025, stable, with 35 components. Base UI by default The detail that changes everything: since July 2026, Base UI is the default in shadcn. You scaffold a new project, it comes up on Base UI, not Radix anymore. What changes (spoiler: not perf) The point is somewhere other than benchmarks. It's literal, you can see it in the code. Three examples. Radix's asChild becomes a render prop. Less magic hidden in a Slot, you see exactly what you're rendering: // Radix <Dialog.Trigger asChild> <Button>Ouvrir</Button> </Dialog.Trigger> // Base UI <Dialog.Trigger render={<Button>Ouvrir</Button>} /> Popup positioning is no longer driven by props on Content , but by an explicit Positioner that wraps the Popup : // Radix <DropdownMenu.Content side=\"left\" align=\"start\">…</DropdownMenu.Content> // Base UI <DropdownMenu.Positioner side=\"left\" align=\"start\"> <DropdownMenu.Popup>…</DropdownMenu.Popup> </DropdownMenu.Positioner> And on the dependencies side, no more constellation of @radix-ui/react-* : a single native package. # Radix npm i @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-popover # Base UI npm i @base-ui/react More verbose in places, sure. But more honest about what's actually happening. Bonus: Base UI ships primitives Radix never had — multiple Select, Combobox, Autocomplete, Drawer. The real reason: the checkbox Let's be honest: everything above is after-the-fact rationalization. The real reason is psychological. There's that category of people who, in a game, can't put the controller down until the bar hits 100%, every trophy unlocked, every box ticked. They're called completionists. I'm one of them. And \"recommended, available, stable, official default\" is exactly the kind of box I can't leave unticked. It's not tech debt I'm paying down, it's a 100% I'm unlocking. Do we migrate or not? shadcn says it himself: the worst thing you can do to a production app is switch component libraries. He's right. You don't have to migrate — Radix is still mature, tested, supported, and nothing rots if you don't move. So the reasonable advice: stable app in production → don't touch a thing living projects, templates, side-projects → migrate while it's painless and the API gap is still small either way, there's an official migration skill (Claude Code, Cursor) that works component by component, one commit each, rollback = you delete the branch. Count on 20 minutes. Anyway, do whatever you want. But for me, the box is ticked."
  }
}