Skip to main content
EJ Centeno

How shadcn/ui Helped Me Build Consistent Enterprise UI Fast

March 7, 2026 · 5 min read

How shadcn/ui Helped Me Build Consistent Enterprise UI Fast

When I joined C3 Interactive Manila in February 2026 and got assigned to the stock administration system project, one of the first architectural decisions already made for me was the UI library: shadcn/ui. I'd heard of it but hadn't used it before. Coming from Vue projects where we used Vuetify — a comprehensive Material Design component library with a large built-in component set — I was curious what the experience would be like.

Six weeks in, shadcn/ui has become one of my favorite tools in the React ecosystem. Here's why.

What Makes shadcn/ui Different

Most UI libraries work the same way: you install an npm package, import components, and style them with props or className overrides. The component code lives in node_modules, inaccessible and often difficult to customize deeply without fighting the library's internal assumptions.

shadcn/ui takes a different approach: you copy the component code directly into your project. When you run the add command, it creates a button.tsx or dialog.tsx file in your components/ui directory. That's your code now. You own it completely.

This has real consequences. Need to change how a Dialog animates? Open the file and change it. Need to add a data attribute to every Table cell for testing purposes? Add it directly. No prop gymnastics, no CSS specificity wars with library styles. The component is just code in your codebase.

The Components We Actually Use

For the stock administration system, the components we reach for most are:

DataTable — built on top of TanStack Table, the DataTable pattern from the shadcn/ui documentation gave us a solid foundation for all our data grids. We've extended it with custom column filter inputs, row selection, expandable rows, and sticky headers. Because we own the code, every extension was a straightforward edit.

Dialog — used for every confirmation modal and data entry overlay in the app. The Dialog component composes Radix UI's Dialog primitives, giving us accessible modal behavior out of the box: focus trapping, Escape key to close, proper ARIA roles. We didn't have to think about any of this — it came with the component.

Form — the Form component integrates with React Hook Form and provides FormField, FormItem, FormLabel, FormControl, and FormMessage components that handle the boilerplate of connecting validation state to UI. Every form in the app uses this pattern.

Select — dropdown selects with proper accessibility. Used for every filter dropdown, plan period selector, and enum field in forms. The Select component handles keyboard navigation, screen reader announcements, and positioning automatically.

Sheet — a slide-over panel used for editing records without leaving the current view. This pattern works well for the settings drawer and participant detail views where context switching would be disruptive.

Theming and Client-Specific Design

shadcn/ui uses CSS custom properties for theming. The default theme defines variables like --background, --foreground, --primary, --muted, and --border. Changing the theme is a matter of changing these CSS variable values in your global stylesheet.

For our client's brand requirements, we updated the primary color values and adjusted the border radius. Two changes in the theme file affected every component simultaneously. The consistency you get from this approach is significant — there's no risk of one component using a slightly different shade of the brand color.

Dark mode support works the same way. We define a dark class variant that overrides the same CSS variables with darker values. Every component responds correctly without any component-level dark mode code.

The Copy-Paste Philosophy in Practice

The main tradeoff with shadcn/ui is that you're responsible for updates. When the library releases a component update with a bug fix or accessibility improvement, you have to manually apply that change to your copied files.

In practice, this has been less painful than expected. Components are small and readable. The library's GitHub repository makes it easy to see what changed between versions. Diffs from upstream changes are typically straightforward to apply. And because we own the code, we never have to wait for a library maintainer to accept a pull request for something we need.

For an enterprise project where consistency and control matter more than automatic updates, this is the right tradeoff. shadcn/ui has been one of the best decisions on this project stack.

← Back to all posts