Why I Like shadcn/ui for Real Projects
September 24, 2026 Avishka Devinda
September 24, 2026 Avishka Devinda
I have used component libraries that look great during the first week of a project and become frustrating during the second month.
The problem usually starts when I need to change something the library did not expect.
That is one reason I like shadcn/ui.
The important difference is simple: I get the component code inside my project.
With many UI libraries, I install a package and import a component from node_modules.
With shadcn/ui, the CLI adds the component source code to my application.
For example:
npx shadcn@latest add button
Now the button lives inside the project and I can edit it.
That changes how I think about the UI layer.
I am not only consuming a component library. I am building my own component system using a good starting point.
This is the biggest advantage for me.
If a button needs a new variant, I can add it.
If a dialog needs different behavior, I can change it.
If I want different spacing, animations, borders, or focus states, I do not have to fight a closed abstraction.
I own the component.
That is useful when a project has a custom design instead of looking like the default theme of a UI package.
Owning the code would not matter if the starting components were poor.
The reason the approach works is that the defaults are already useful.
I get common pieces such as:
Then I customize them for the product.
Most of my projects already use Tailwind.
That makes shadcn/ui feel natural because the styling is visible in the component code.
I can see why an element has a specific radius, spacing, border, or hover state.
There is less mystery.
For example, a project-wide design change can often be handled with CSS variables and a few component updates instead of overriding dozens of package styles.
Having full control can also become a problem.
If every page modifies the same component differently, the design system slowly disappears.
I try to keep the base components generic.
Then I compose them into product-specific components.
For example:
components/ui/button.tsx
components/ui/dialog.tsx
components/billing/upgrade-dialog.tsx
components/projects/delete-project-dialog.tsx
The UI folder contains reusable primitives.
Feature folders contain the product behavior.
A clean design is not enough.
Components such as dialogs, dropdowns, and menus need keyboard behavior, focus management, and proper semantics.
Starting from accessible primitives saves me from rebuilding those interactions from scratch every time.
I still test the final implementation because customization can break good defaults.
I only add what the application needs.
A project with ten shadcn/ui components is easier to maintain than one where I added the entire catalog "just in case."
The same applies to blocks.
They are useful starting points, but I still remove code that does not belong in the final product.
I like building interfaces that have their own visual identity.
I do not want the UI library to become the design.
shadcn/ui gives me enough structure to move quickly while still allowing me to control the final result.
That balance is valuable.
The reason I like shadcn/ui is not because it has more components than every other library.
It is because it gives me good component code and then gets out of the way.
I can keep the accessibility and composition patterns, change the visual design, and make the components feel like they belong to the application.
For custom Next.js projects, that is usually exactly what I want.