Why I Stopped Building UI Components from Scratch (Shadcn Converted Me)

By amillionmonkeys
#UI Components#React#Web Development

After years of custom components, shadcn/ui changed how I start projects. Copy-paste components that don't feel like a framework straitjacket.

I spent years building UI components from scratch. Dropdowns, modals, date pickers, command palettes—every project started with the same 20-hour overhead of recreating the same patterns. I told myself it gave me control. I wasn't wrong, but I wasn't entirely right either.

Then I tried shadcn/ui on a client project last year. Six months and a dozen projects later, I barely write custom components anymore. Here's what changed my mind—and what you should know if you're considering it.

The Custom Component Tax We Were Paying

Building components from scratch sounds great in theory. You get exactly what you want, no bloat, complete control. The reality looked different.

Every new project meant:

  • 2-3 days building a base component library
  • Another week fixing accessibility edge cases I forgot
  • Ongoing maintenance as browsers and React versions changed
  • Freelancer onboarding teaching collaborators my custom patterns

I hit the same bugs repeatedly. Focus management in modals. Keyboard navigation in dropdowns. ARIA attributes I'd miss in the initial build and patch in later when a client mentioned accessibility.

The worst part? Most of my custom work wasn't adding value. I was rebuilding the same select menu for the fifteenth time, not solving interesting problems.

What shadcn/ui Actually Is (Not Another Component Library)

Here's what confused me initially: shadcn/ui isn't a package you install.

There's no dependency. There's no import { Button } from 'shadcn-ui'. Instead, you run a CLI command that copies component source code directly into your project:

npx shadcn-ui@latest add button

This generates a file in your codebase—typically components/ui/button.tsx—that you own completely. It's your code now. You can modify it, rename it, delete half of it. There's no framework to fight.

The components are built on Radix UI primitives for the complex stuff (accessibility, keyboard navigation, focus management) and use Tailwind CSS for styling. But you're not locked into either choice.

Why This Approach Changed Everything for Me

The first project I used shadcn/ui on was a fintech dashboard with complex data tables and filtering. I needed it done in four weeks.

I copied in a dozen components on day one: Button, Dialog, Select, Command, Table. Then I spent the next three weeks actually building features instead of reinventing dropdowns.

What made the difference:

1. Accessibility came free Every component handled focus trapping, ARIA attributes, keyboard navigation out of the box. Not as an afterthought—as the foundation. Radix UI underneath meant I got years of accessibility work from the community.

2. Customization without friction Need a button variant? Just edit the button component in your codebase. Want to change the default animation? Update the Tailwind config. There's no abstraction layer fighting you.

// The button component lives in your repo
// Change anything you want
export const Button = React.forwardRef<
  HTMLButtonElement,
  ButtonProps
>(({ className, variant, size, ...props }, ref) => {
  return (
    <button
      className={cn(buttonVariants({ variant, size, className }))}
      ref={ref}
      {...props}
    />
  )
})

3. No framework straitjacket This is what the title means: you're not locked into someone else's decisions. Don't like how the dialog works? Rewrite it. Want to use a different styling approach? Nothing stops you. It's just React components in your repository.

I've shipped projects where I heavily customized shadcn components and projects where I barely touched them. Both approaches work fine.

4. Fast project starts Instead of the 20-hour component setup tax, I'm building features on day one. Add the components you need, customize if necessary, ship.

When shadcn/ui Works (And When It Doesn't)

After using it across different project types, here's what I've learned:

Works great for:

  • Typical web apps with standard UI needs (dashboards, admin panels, SaaS products)
  • Projects with tight timelines where you can't justify custom component development
  • Teams comfortable with Tailwind (or willing to adapt the components)
  • Accessibility-first projects where you need it built in, not bolted on

Less ideal for:

  • Highly custom design systems where components need fundamentally different behavior
  • Design-first brands where visual uniqueness is the primary value
  • Projects avoiding Tailwind (though you can adapt the components, it's more work)
  • Non-React projects (obviously—it's React-specific)

I still build custom components sometimes. When a client needs something truly unique, or when their design system diverges significantly from shadcn's patterns, I go custom. But that's maybe 20% of projects now, not 100%.

The Radix UI Foundation Matters

Worth emphasizing: shadcn/ui isn't magic. It's a really good implementation layer on top of Radix UI primitives.

Radix handles the hard parts:

  • Focus management and keyboard navigation
  • Screen reader announcements
  • Portal rendering for overlays
  • Proper ARIA attributes
  • Browser compatibility

shadcn/ui adds:

  • Sensible default styles
  • Consistent API patterns
  • Good documentation
  • Easy customization

This layering is why it works. You get accessibility from Radix (battle-tested across thousands of projects) and flexibility from shadcn's implementation approach.

How I Use It Now

My typical project setup:

  1. Start with shadcn/ui base - Add 8-10 core components (Button, Dialog, Select, Input, etc.)
  2. Customize immediately - Adjust colors, spacing, animations to match the project
  3. Build project-specific components - Compose shadcn primitives into domain components
  4. Add custom when needed - Build from scratch only when shadcn doesn't fit

This hybrid approach gives me speed without sacrificing flexibility.

For example, on a recent mobile app development project, I used shadcn/ui for the admin dashboard but built completely custom components for the consumer-facing mobile UI. Different tools for different jobs.

Compared to Other Approaches

vs Material UI / Ant Design: Full component libraries lock you into their design language and API. Customization fights the framework. shadcn gives you the source code to modify directly.

vs Headless UI / Radix UI directly: These give you unstyled primitives. You still need to style everything and build the component API yourself. shadcn provides styled, ready-to-use implementations while keeping access to the primitives.

vs Building from scratch: Custom work gives you complete control but costs significantly more time. For most projects, shadcn's customization is enough control at a fraction of the time investment.

I'm not suggesting shadcn/ui is always the right choice. But for the majority of web applications I build in Brighton, it's become the default starting point.

What I Learned After Six Months

Some unexpected discoveries:

Consistency across projects improved. When using the same base components across projects, codebases feel familiar. Onboarding freelancers got easier.

I spent more time on features. Less time building dropdowns meant more time solving actual business problems. Clients noticed.

Accessibility compliance improved. I went from "I'll add ARIA later" to "it's built in from day one." Compliance testing got faster.

Customization was easier than expected. I worried I'd hit walls. I haven't. Every time I needed to customize something, the code was right there to modify.

The community matters. When I hit edge cases, someone else usually had already solved it. The shadcn Discord and GitHub discussions are surprisingly active and helpful.

Getting Started

If you want to try it:

  1. Read the shadcn/ui installation guide
  2. Start with a small project, not your flagship product
  3. Add a few components and see how they feel
  4. Customize something to test the workflow
  5. Decide if the approach fits your team

The CLI makes setup straightforward. You'll have working components in minutes.

The Bottom Line

I still build custom components when projects demand it. But for most web applications, shadcn/ui gives me 90% of what I need in 10% of the time.

The copy-paste approach means I'm not fighting a framework. The Radix foundation means accessibility is handled. The Tailwind integration means customization is fast.

After years of custom component development, I didn't expect a different approach to change my workflow this much. But here I am: converted.

Planning a React project and deciding on a UI approach? I've now used shadcn/ui across a dozen production applications and learned which patterns work best. Get in touch if you'd like to discuss your specific needs.


Related reading:

T: 07512 944360 | E: [email protected]

© 2025 amillionmonkeys ltd. All rights reserved.