shadcn/ui, code in your repository and the move to Base UI
shadcn/ui gets described as a component library, and that is the biggest misconception about it. It is not a dependency you install and update. It is a way of distributing code: you run a command and it copies component files into your repository, where they become your code.
That difference looks like a detail and explains practically everything happening to this project, the most important change of July 2026 included.
The foundation moved to Base UI
Components need a layer underneath: mechanisms handling focus, keyboard navigation, overlays, and accessibility. For years Radix filled that role.
Since July 2026 new projects start on Base UI by default, a library developed by the team behind MUI, which reached its first stable version in December 2025. The documentation now leads with Base UI, and the Radix variant sits one click away.
The most important thing for somebody with a running project: Radix remains fully supported, and projects built on it need not migrate. This is not a wind down but a change of default.
And here we return to the first paragraph. That change was possible without breaking anything precisely because the components live in users' repositories. There is no package that could bump a major version and force everybody to migrate. New projects get a new foundation, old ones keep theirs, and both work.
For those wanting to move, what exists is not a command line tool but a skill for a coding agent: you install a description of the differences between the two libraries, then ask the agent to move a particular component. The authors explain the choice plainly: an automated transform would handle the files you never touched and fall over on the ones you changed. It works progressively: you move one component and its call sites, the project keeps building throughout, and both libraries coexist meanwhile. That is the right approach, since migrating twenty components in one move is risky.
An earlier change worth knowing
In February 2026 the components this tool generates moved to the umbrella Radix package. Previously every component imported from a package of its own, today it imports from the shared one. Precision matters here: this is a change of imports in generated code rather than a withdrawal of anything. The per component packages are still being released and are not marked deprecated, and the umbrella package has existed since early 2025.
The practical consequence concerns projects that have not updated for a while. Imports from a per component package still work, while when adding new components it pays to unify imports, since mixing two approaches in one project can pull in two versions of the same thing. Unifying them has a dedicated migration command, and that one really is a command rather than work for an agent.
That is in fact characteristic of this project: changes come often, and material online ages within months. A tutorial's date matters more here than with most libraries.
Why copying code makes sense
Worth justifying the approach, since it sounds like a step back from an ordinary dependency.
An ordinary component library gives convenience at the start and a wall later. While a component does what you want, everything is fine. Once you need to change something its author did not anticipate, you start overriding styles, fighting selectors, and hunting for workarounds.
Here that wall does not exist, since the component is your file. You change it like any other code in the project, asking nobody's permission and waiting for no new version.
The price is equally concrete. You get no updates. A bug fix made upstream will not reach you by itself, so you either track changes and carry them over by hand or live with the version you copied. Across twenty components that is real work.
A practical rule emerges after a few projects. Copy components you will modify, and for those you do not intend to modify, consider an ordinary dependency. A button and a text field almost always get reworked, so they fit here. A complex chart or a rich text editor less often, so a library is sometimes better there.
How it works in practice
pnpm dlx shadcn@latest init
pnpm dlx shadcn@latest add button dialog formThe first command sets the project up: Tailwind configuration, colour variables, helper functions, and paths. The second copies the named components into a directory you chose yourself.
From that moment the files are yours. Changing a button variant means editing a file rather than hunting for a property in documentation.
Note one thing on a first run. The initialisation command asks questions about project structure and records the answers in a configuration file.
{
"$schema": "https://ui.shadcn.com/schema.json",
"tsx": true,
"tailwind": {
"css": "src/app/globals.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"ui": "@/components/ui",
"utils": "@/lib/utils"
}
}That file then decides where later components land, so it deserves reviewing rather than clicking through the questions. The colour variables entry especially: flipping it later means rewriting everything produced so far.
On a new project a foundation choice joins that. The tool accepts a flag naming the mechanism library, so the decision is explicit rather than hidden in a default. There are three values rather than two: alongside Base UI and Radix, React Aria arrived in July 2026 and is treated as an equal since, with its own documentation and its own registry.
Theming and colour variables
The visual layer rests on variables rather than classes written directly into components, and that decision carries larger consequences than it appears.
Colours are described as variables named by role rather than by hue. A component does not know a button is blue; it knows only that it uses the primary colour.
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--destructive: oklch(0.577 0.245 27.325);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
}The names come in pairs: a background colour and the text colour that contrasts sufficiently against it. That solves a problem easy to forget when swapping a theme, since changing the background alone produces a result faithful to the intent and unreadable.
The gain shows in two places. Dark mode reduces to swapping variable values rather than writing variants of every component. Changing an application's whole palette means editing one file rather than walking through fifty component files.
The price is that role names need thinking through at the start. The default set is sensible and general, while a product with its own visual system usually needs a few extra roles, and adding them a year later means reviewing everything already built.
Practical advice: extend the variable set before copying twenty components rather than after. Adding two roles at the beginning costs fifteen minutes; the same thing after six months of work is a separate task.
Keep the variables in one place too, and treat that file as the boundary between the visual system and the code. A component reaching for a specific shade rather than a role is a breach that spreads through a project unless somebody stops it at review.
Accessibility and what stays invisible
Worth stating what the underlying layer actually handles, since that is the main reason for using it at all rather than writing components from scratch.
A dialog is not an element with a border. It is a focus trap so the keyboard cannot leave the dialog, handling of the closing key, restoring focus after closing, blocking background scrolling, announcing the change to a screen reader, and behaviour on a touch device. Each of those can be written yourself, and each carries edge cases you learn about from an audit report.
A dropdown is worse still, adding arrow key navigation, selecting by typing the first letters, behaviour while scrolling, and positioning against screen edges.
That is precisely the part invisible in the code copied into a repository, since it lives in the dependency underneath. The July 2026 foundation change concerned exactly that layer, which is why it was noticeable for new projects and invisible for existing ones.
The practical conclusion when modifying copied components: change styles and layout freely, and think twice about behaviour and accessibility attributes.
<DialogContent className="max-w-2xl gap-8 p-10">
<DialogTitle>Delete this order</DialogTitle>
<DialogDescription>
This operation cannot be undone.
</DialogDescription>
</DialogContent>The classes on the first line are a safe change. Removing the title or description element because they look visually redundant is not: a screen reader then loses the only statement of what the open dialog is, and the development mode warning about it is easy to dismiss as noise.
shadcn/ui against the alternatives
| Option | Form | Updates | Pick it when |
|---|---|---|---|
| shadcn/ui | Code in your repository | Manual, by re-adding | Your own visual system, changes needed |
| Radix or Base UI directly | A dependency, no styles | Automatic | You want to write every style yourself |
| A styled library | A dependency with styles | Automatic | An internal dashboard, a fast start |
| Components written from scratch | Your code | Yours | Very unusual requirements |
The second row deserves consideration and gets skipped. If you are going to rewrite every style anyway, an intermediate layer carrying a ready appearance adds little, and copying code you will immediately change is a detour. Reaching directly for the mechanism library gives the same thing without the middleman.
The third row wins where appearance is not a differentiator. An admin dashboard used by twenty people needs no visual system of its own, and a styled library saves weeks.
The first row is right when you hold your own visual system or know you will change component behaviour. Then copying code is an advantage rather than a compromise.
The same copy the code model has grown collections of ready made elements worth using selectively. Aceternity UI offers components with 3D effects and animation, while Uiverse gathers community elements in plain CSS and Tailwind. Treat them as a catalogue of inspiration rather than a dependency: the code lands in your repository on the same terms, so its quality and accessibility become yours.
The registry and your own components
The distribution mechanism is not closed, and that is a capability few people know about.
A registry is a description of components in a defined format, available at an address. The tool can fetch from registries other than the default, so a company can publish its own set and hand it to teams with the same command.
That solves a real problem in a larger organisation. A visual system shipped as a package forces updates and versioning, while distributed as code it lets each team adapt a component to its case while keeping a shared starting point.
Whole third party libraries use the same mechanism. The best example is coss ui, formerly Origin UI, which gave up its own installer and publishes components through a registry namespace, so you add them with exactly the same command as shadcn elements, on the same Base UI foundation.
When building your own registry, state in the configuration which mechanism library your components sit on. Without that they get treated as based on the default, which since the July change means Base UI rather than Radix.
Know too that interface generating tools, v0 for instance, emit code in this convention. That is convenient, since the output slots into a project without translation, and worth remembering when judging whether such a generator fits your stack at all.
Common mistakes
The first is treating this as a dependency and expecting updates. Upstream fixes do not arrive by themselves, so they need carrying over deliberately or accepting the version you copied.
The second is copying every available component at the start. The repository fills with files nobody uses, and during code review live code cannot be told from code copied just in case.
The third is learning from material without checking the date. This project changed its foundation and package structure within one year, so a year old tutorial describes a different state.
The fourth is migrating every component at once. The agent led move works progressively precisely so the project keeps building, and moving in one sweep gives that advantage away.
The fifth is changing a copied component without recording what changed and why. Re-adding the same component wipes the changes, and nobody remembers they existed.
The sixth is choosing this approach for an internal dashboard where appearance does not matter. A styled library is faster there and cheaper to maintain.
The seventh is stripping accessibility attributes while reworking a component. They look like redundant markup and handle keyboard and screen reader support, meaning the part this dependency was added for in the first place.
The eighth is writing specific shades instead of variables naming a role. A single occurrence does not hurt, while it spreads through a project, and a year later dark mode requires walking through every component rather than one file.
FAQ
Is shadcn/ui a component library?
Not in the ordinary sense. It is a tool copying component code into your repository, where the files become yours. There is no dependency to update and no version to bump, and changing a component means editing a file.
What changed in July 2026?
New projects start on Base UI by default rather than Radix. Radix remains fully supported, existing projects need not migrate, and for those wanting to move there is a skill for a coding agent that migrates progressively, component by component. The same month brought React Aria as a third equal base.
Do I have to migrate off Radix?
No. The change concerns the default for new projects rather than a wind down. Projects built on Radix keep working and will keep working, so migration is a choice rather than a necessity.
How do I get fixes made upstream?
By hand. You can re-add a component and compare differences, or track changes in the source project and carry over those that concern you. That is the price of full control over the code, and it deserves costing out across many components.
Can I build my own registry?
Yes, the distribution mechanism accepts registries other than the default, so a company can hand out its own visual system with the same command. State the mechanism library in the configuration then, since without it the components get treated as based on the default.
The foundation change is described in the project changelog, and the documentation on the project site.