Software Engineering & Digital Products for Global Enterprises since 2006
CMMi Level 3SOC 2ISO 27001
View all services
Staff Augmentation
Embed senior engineers in your team within weeks.
Dedicated Teams
A ring-fenced squad with PM, leads, and engineers.
Build-Operate-Transfer
We hire, run, and transfer the team to you.
Contract-to-Hire
Try the talent. Convert when you're ready.
ForceHQ
Skill testing, interviews and ranking — powered by AI.
RoboRingo
Build, deploy and monitor voice agents without code.
MailGovern
Policy, retention and compliance for enterprise email.
Vishing
Test and train staff against AI-driven voice attacks.
CyberForceHQ
Continuous, adaptive security training for every team.
IDS Load Balancer
Built for Multi Instance InDesign Server, to distribute jobs.
AutoVAPT.ai
AI agent for continuous, automated vulnerability and penetration testing.
Salesforce + InDesign Connector
Bridge Salesforce data into InDesign to design print catalogues at scale.
HumanDISC
AI-powered behavioral assessments and DISC profiling for smarter hiring.
View all solutions
Banking, Financial Services & Insurance
Cloud, digital and legacy modernisation across financial entities.
Healthcare
Clinical platforms, patient engagement, and connected medical devices.
Pharma & Life Sciences
Trial systems, regulatory data, and field-force enablement.
Professional Services & Education
Workflow automation, learning platforms, and consulting tooling.
Media & Entertainment
AI video processing, OTT platforms, and content workflows.
Technology & SaaS
Product engineering, integrations, and scale for tech companies.
Retail & eCommerce
Shopify, print catalogues, web-to-print, and order automation.
View all industries
Blog
Engineering notes, opinions, and field reports.
Case Studies
How clients shipped — outcomes, stack, lessons.
White Papers
Deep-dives on AI, talent models, and platforms.
View all resources
About Us
Who we are, our story, and what drives us.
Co-Innovation
How we partner to build new products together.
Careers
Open roles and what it's like to work here.
News
Press, announcements, and industry updates.
Leadership
The people steering MetaDesign.
Locations
Gurugram, Brisbane, Detroit and beyond.
Contact Us
Talk to sales, hiring, or partnerships.
Request TalentStart a Project
Web Development

How We Migrated a React App from MUI to shadcn/ui: Building a Design System That AI Can Understand

MS
MetaDesign Solutions
Frontend Architecture Team
September 17, 2026
18 min read
How We Migrated a React App from MUI to shadcn/ui: Building a Design System That AI Can Understand — Web Development | MetaDesign Solutions

Introduction: Why We Replaced MUI with shadcn/ui

Migrating a production React application from Material UI (MUI) to shadcn/ui involves replacing a monolithic, runtime-styled component library with a composable, Tailwind CSS-based component architecture that gives you full ownership of every component's source code. The result is dramatically smaller bundles, faster load times, a consistent design system, and a codebase that AI coding assistants like Claude, Cursor, and GitHub Copilot can understand and extend reliably.

Written in September 2026, this case study documents how MetaDesign Solutions executed this migration for a client's enterprise SaaS dashboard — a React application with 120+ screens, 40+ shared components, and a growing frustration with MUI's performance overhead and styling inconsistencies. The client's development team had adopted AI-augmented development workflows → but found that AI tools consistently generated inconsistent code because the existing codebase lacked a clearly defined, discoverable design system.

This post covers the full journey: the audit that exposed the problems, the architecture decisions behind the new component library, the migration strategy that avoided a risky big-bang rewrite, and the rules.md file we created to make the codebase genuinely AI-native.

The Problem: MUI at Scale Creates Hidden Technical Debt

Material UI is an excellent library for rapid prototyping and getting to market fast. But as applications scale beyond 50+ screens, several problems emerge that compound over time:

1. Bundle Size Bloat

Our audit of the client's application revealed alarming numbers:

MetricBefore (MUI)Industry TargetSeverity
Total JS bundle (gzipped)1.8 MB< 300 KBCritical
MUI + Emotion CSS-in-JS420 KBN/AMajor contributor
First Contentful Paint4.2s< 1.8sCritical
Largest Contentful Paint6.1s< 2.5sCritical
Time to Interactive8.7s< 3.8sCritical

2. No Coherent Design System

Over two years of feature development, the codebase had accumulated six different ways to style components: MUI's sx prop, styled() API, inline style attributes, external CSS files, CSS modules, and makeStyles from the deprecated MUI v4 API that was never cleaned up. New developers spent their first week just figuring out which styling approach to use.

3. Unnecessary Library Sprawl

The dependency audit found 14 UI-related packages that overlapped with MUI's own capabilities — separate date pickers, icon libraries, modal packages, toast libraries, and chart wrappers. Many were installed once for a single feature and never consolidated.

4. AI Tools Produced Inconsistent Code

When developers used Claude, Cursor, or Copilot to generate new components, the AI had no way to determine the project's "correct" styling approach. It would generate MUI components with sx props in one file, styled() in another, and raw CSS in a third — directly mirroring the inconsistency already in the codebase. There was no single source of truth for the design system.

Phase 1: Full Codebase Audit and Component Inventory

Before writing a single line of migration code, we invested two weeks in a systematic audit. This phase is critical — skipping it leads to scope creep, missed components, and regressions.

Component Census

We catalogued every MUI component used across the application using static analysis:

npx depcheck --json | jq '.dependencies' # Unused packages
grep -rn "from '@mui" src/ | awk -F'/' '{print $NF}' | sort | uniq -c | sort -rn
grep -rn "from 'react-" src/ | awk -F"'" '{print $2}' | sort | uniq -c | sort -rn

The results showed:

  • 38 unique MUI components used across the app (Button, TextField, Dialog, DataGrid, Autocomplete, etc.)
  • 14 external UI packages providing overlapping functionality
  • 23 components used only once — candidates for inlining or removal
  • 9 custom wrapper components that simply proxied MUI with one or two overridden props

Styling Pattern Audit

We mapped every styling approach to its usage count:

Styling MethodFile CountDecision
MUI sx prop187Replace with Tailwind classes
MUI styled() API64Replace with Tailwind + cn()
Inline style attributes92Replace with Tailwind classes
CSS Modules31Migrate to Tailwind
Deprecated makeStyles43Remove entirely
External CSS files28Consolidate into Tailwind

Performance Profiling

Using Lighthouse, WebPageTest, and Chrome DevTools Performance panel, we identified the primary bottlenecks:

  • Emotion CSS-in-JS runtime: Generating styles at runtime added ~200ms to every page transition
  • MUI ThemeProvider: Context re-renders propagated through the entire component tree on any theme token access
  • Unused MUI modules: Tree-shaking was partially effective, but Emotion's dynamic styles resisted static elimination
  • Duplicate icon bundles: Both @mui/icons-material (2,100+ icons) and react-icons were installed — only 47 unique icons were actually used

Why shadcn/ui: The Architecture Decision

We evaluated four alternatives to MUI before recommending shadcn/ui to the client:

LibraryBundle ImpactCustomizationAI CompatibilityVerdict
Ant DesignSimilar to MUITheme tokensModerateRejected — same class of problems
Chakra UILighter, still runtimeStyle propsModerateRejected — runtime CSS
Radix Primitives (raw)MinimalFull controlGoodToo much boilerplate
shadcn/uiZero runtimeFull source ownershipExcellentSelected

shadcn/ui won decisively for three architectural reasons:

  1. Copy-paste ownership: Components are copied into your project as source files in src/components/ui/. There is no node_modules dependency — you own the code. This means you can modify, extend, and delete components without worrying about upstream breaking changes. AI tools can read the actual component source, not guess at library internals.
  2. Tailwind CSS-only styling: No runtime CSS-in-JS. Every style is a Tailwind utility class compiled at build time into a single, minimal CSS file. This eliminates Emotion's runtime overhead entirely — no style injection, no context providers, no re-renders from theme changes.
  3. Radix Primitives foundation: shadcn/ui builds on Radix UI primitives for accessibility-critical components (Dialog, Dropdown, Tooltip, Tabs). This gives you WAI-ARIA compliance without reinventing focus management, keyboard navigation, or screen reader support.

Explore MDS App Modernization Services →

Phase 2: Designing the Component Library

Before migrating a single component, we designed the target architecture. This upfront investment saved weeks of rework and ensured every team member — human and AI — would build consistently.

Directory Structure

src/
  components/
    ui/                    # shadcn/ui base components (Button, Input, Dialog, etc.)
    composed/              # Multi-component compositions (SearchBar, DataTable, StatCard)
    layouts/               # Page-level layouts (DashboardLayout, AuthLayout)
    providers/             # Context providers (ThemeProvider, ToastProvider)
  lib/
    utils.ts               # cn() helper, formatters, validators
  styles/
    globals.css            # Tailwind directives + CSS custom properties
    tokens.css             # Design tokens (colors, spacing, typography)
  hooks/                   # Shared React hooks

Design Token Architecture

We defined a design token system using CSS custom properties that Tailwind consumes. This creates a single source of truth for the entire visual language:

/* tokens.css */
:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --primary: 221.2 83.2% 53.3%;
  --primary-foreground: 210 40% 98%;
  --secondary: 210 40% 96.1%;
  --muted: 210 40% 96.1%;
  --muted-foreground: 215.4 16.3% 46.9%;
  --destructive: 0 84.2% 60.2%;
  --border: 214.3 31.8% 91.4%;
  --ring: 221.2 83.2% 53.3%;
  --radius: 0.5rem;
}

.dark {
  --background: 222.2 84% 4.9%;
  --foreground: 210 40% 98%;
  /* ... dark mode overrides */
}

Component API Standards

Every component in the library follows strict API conventions, documented for both human developers and AI assistants:

  • Variants via CVA: All visual variants use class-variance-authority (CVA) — variant, size, color props map to predefined Tailwind class sets
  • Composition via cn(): The cn() utility (built on clsx + tailwind-merge) is the only way to merge classes — no string concatenation, no conditional ternaries in className
  • Forwarded refs: Every component uses React.forwardRef for DOM access compatibility
  • Typed props: Every component exports its props type — ButtonProps, InputProps, etc.

Phase 3: Incremental Migration Strategy — No Big Bang

We rejected a big-bang rewrite in favor of an incremental, page-by-page migration that kept the application deployable throughout the entire process. Here is the strategy we followed:

Step 1: Install shadcn/ui Alongside MUI (Week 1)

Both libraries coexist during migration. Tailwind CSS was added and configured to ignore MUI-rendered elements using Tailwind's important strategy to prevent class conflicts:

// tailwind.config.ts
export default {
  // Prevent Tailwind from affecting MUI components during migration
  important: '#app-root',
  content: ['./src/**/*.{ts,tsx}'],
  theme: {
    extend: {
      colors: {
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
        primary: { DEFAULT: 'hsl(var(--primary))', foreground: 'hsl(var(--primary-foreground))' },
        // ... map all design tokens
      },
    },
  },
} satisfies Config;

Step 2: Migrate Leaf Components First (Weeks 2-3)

We started with the simplest, most-used components — Button, Badge, Input, Card — that had no child component dependencies. Each migration followed a checklist:

  1. Install the shadcn/ui component: npx shadcn@latest add button
  2. Create a compatibility wrapper that accepts the old MUI props API
  3. Run the component through Storybook visual regression tests
  4. Search-and-replace all imports from @mui/material/Button to @/components/ui/button
  5. Remove the compatibility wrapper once all consumers use the new API
  6. Delete the old MUI imports and verify no references remain

Step 3: Migrate Complex Components (Weeks 4-6)

DataGrid, Autocomplete, DatePicker, and Dialog required more careful handling. For DataGrid, we built a custom DataTable component using @tanstack/react-table + shadcn/ui's Table primitives. The result was a component that was 4x smaller and gave us full control over column rendering, sorting, filtering, and pagination.

Step 4: Remove MUI Entirely (Week 7)

npm uninstall @mui/material @mui/icons-material @mui/x-data-grid \
  @mui/x-date-pickers @emotion/react @emotion/styled \
  @mui/lab @mui/styles

After the final MUI removal, we ran a full build and verified zero references to @mui remained in the codebase.

Phase 4: Eliminating Unnecessary Dependencies

With MUI gone, we turned to the 14 overlapping UI packages that had accumulated over two years. Each was evaluated against a simple criteria: does shadcn/ui or Tailwind already provide this capability?

Removed PackageSizeReplaced With
react-modal32 KBshadcn/ui Dialog (Radix)
react-toastify18 KBshadcn/ui Toast (Sonner)
react-select45 KBshadcn/ui Combobox (cmdk)
react-datepicker52 KBshadcn/ui DatePicker (react-day-picker)
react-tooltip14 KBshadcn/ui Tooltip (Radix)
@mui/icons-material~60 KB (used)lucide-react (47 icons cherry-picked)
react-icons~80 KB (used)lucide-react (consolidated)
notistack24 KBRemoved (duplicate of react-toastify)

In total, we removed 18 npm packages and replaced them with 3 — shadcn/ui's Radix primitives, @tanstack/react-table, and lucide-react.

Expert Solutions for Web Development

Need help with Web Development? Our engineering team builds production-ready solutions tailored to your enterprise workflows.

Book a free consultation

Phase 5: Making the Codebase AI-Native with rules.md

This was the most forward-thinking phase of the project, and the one the client was most excited about. The goal was to make the codebase self-documenting for AI coding assistants so that every AI-generated component would automatically follow the design system — without developers needing to manually review and correct AI output.

What is rules.md?

A rules.md (or .cursorrules, or CLAUDE.md) file is a project-level instruction file that AI coding platforms read before generating code. It acts as the AI's "onboarding document" — telling it the project's conventions, preferred libraries, component patterns, and anti-patterns to avoid. We created a comprehensive rules.md that Claude, Cursor, and GitHub Copilot Workspace would automatically pick up.

Structure of Our rules.md

# Project Rules for AI Coding Assistants

## Tech Stack
- React 18 + TypeScript (strict mode)
- Tailwind CSS v3.4 for all styling
- shadcn/ui components in src/components/ui/
- Radix UI primitives for accessibility
- @tanstack/react-table for data tables
- lucide-react for icons
- react-hook-form + zod for forms

## Component Creation Rules
1. ALWAYS use components from src/components/ui/ — NEVER install MUI, Chakra, or Ant Design
2. Style ONLY with Tailwind utility classes — NO inline styles, NO CSS modules, NO styled()
3. Use cn() from src/lib/utils for conditional classes — NO string concatenation
4. Every component MUST use React.forwardRef and export its Props type
5. Use CVA (class-variance-authority) for component variants

## Design Tokens
- Colors: Use semantic tokens (bg-primary, text-muted-foreground) — NOT arbitrary values
- Spacing: Use Tailwind scale (p-4, gap-6) — NOT arbitrary px values
- Border radius: Use rounded-md (maps to var(--radius)) — NOT rounded-lg or rounded-xl

## File Conventions
- UI primitives: src/components/ui/[component].tsx
- Composed components: src/components/composed/[component].tsx
- Pages: src/app/[route]/page.tsx
- Hooks: src/hooks/use-[name].ts

## Anti-Patterns (NEVER DO)
- Do NOT use @mui/* or @emotion/* packages
- Do NOT use makeStyles, styled(), or sx prop
- Do NOT use react-modal, react-toastify, react-select (use shadcn equivalents)
- Do NOT create new CSS files — all styles go in Tailwind classes
- Do NOT use arbitrary Tailwind values like bg-[#ff0000] — use design tokens

Why This Matters for AI-Assisted Development

With the rules.md in place, every AI-generated component across the team automatically:

  • Uses shadcn/ui primitives from the local src/components/ui/ directory
  • Styles exclusively with Tailwind utility classes and design tokens
  • Follows the project's naming conventions and file structure
  • Avoids importing any of the removed libraries
  • Uses cn() for class merging, CVA for variants, and zod for validation

The client reported that AI code review rejection rate dropped from 45% to under 8% after the rules.md was introduced — developers spent dramatically less time fixing AI-generated code to match project conventions.

Results: Before and After

After the full migration, the numbers spoke for themselves:

MetricBefore (MUI)After (shadcn/ui)Improvement
JS bundle (gzipped)1.8 MB680 KB62% reduction
First Contentful Paint4.2s1.4s67% faster
Largest Contentful Paint6.1s2.1s66% faster
Time to Interactive8.7s3.2s63% faster
npm dependencies18713453 packages removed
Styling approaches6 different methods1 (Tailwind only)Unified system
AI code rejection rate45%8%82% improvement
Build time94s41s56% faster

Beyond the numbers, the qualitative improvements were equally significant:

  • Developer onboarding: New developers went from "which styling approach do I use?" to productive in a single day
  • Design consistency: Every page now uses the same design tokens, spacing scale, and component variants
  • Dark mode: Switching themes went from a multi-sprint project to a single CSS variable toggle
  • Accessibility: Radix primitives brought WAI-ARIA compliance to Dialog, Dropdown, Tooltip, and Tabs — areas where the previous custom implementations had keyboard navigation gaps

Lessons Learned: What We Would Do Differently

Every migration teaches lessons. Here are the five most impactful takeaways from this project:

  1. Migrate DataGrid last, not first. Complex table components have the most dependencies and edge cases. We made the mistake of starting DataGrid migration in week 3 and it blocked other work. In future migrations, we would tackle DataGrid in the final phase when the rest of the component library is stable.
  2. Create the rules.md on day one. We created the rules file in Phase 5, but in hindsight, it should have been the very first deliverable. Developers were already using AI tools during the migration itself, and without rules, the AI kept generating MUI-style code that then needed re-migration.
  3. Visual regression testing is non-negotiable. We used Chromatic (Storybook-based visual diffing) to catch subtle styling differences between MUI and shadcn/ui implementations. Without it, several components had spacing and alignment regressions that would have reached production.
  4. Keep MUI as a devDependency until final removal. During the coexistence phase, TypeScript's unused import warnings helped us track migration progress. We could run grep -rn "@mui" src/ | wc -l daily to see the count approach zero.
  5. Document the component API mapping before migrating. Creating a spreadsheet mapping every MUI prop to its shadcn/ui equivalent (e.g., MUI's variant="contained" → shadcn's variant="default") saved significant time during the search-and-replace phase.

Should You Migrate from MUI to shadcn/ui?

This migration is not for every project. Here is our honest assessment of when it makes sense:

Migrate If:

  • Your bundle size exceeds 1 MB gzipped and Core Web Vitals are failing
  • Your team has adopted AI coding tools and wants consistent AI-generated output
  • You have 3+ different styling approaches in the same codebase
  • You need full design system ownership without library version lock-in
  • You are building a modern tech stack and want build-time CSS over runtime CSS

Stay on MUI If:

  • Your application is small (< 20 screens) and performance is acceptable
  • Your team prefers Material Design's opinionated visual language and does not need customization
  • You are heavily invested in MUI's DataGrid Pro/Premium for complex enterprise tables
  • Migration cost does not justify the performance gains for your specific use case

Ready to Modernize Your React Frontend?

The MetaDesign Solutions Frontend Architecture team specializes in modernizing legacy React applications — from component library migrations and design system creation to performance optimization and AI-readiness audits. Whether you are struggling with MUI performance, inconsistent styling, or AI tools that generate off-brand code, our engineers can design and execute a migration plan tailored to your application's scale and complexity.

Explore our App Modernization Services →

Or Contact Us → to schedule a free architecture assessment of your current React frontend.

FAQ

Frequently Asked Questions

Common questions about this topic, answered by our engineering team.
Material UI uses Emotion CSS-in-JS which generates styles at runtime, adding significant bundle size (typically 300-400 KB) and causing performance overhead on every render. shadcn/ui uses Tailwind CSS utility classes compiled at build time, eliminating runtime style injection entirely. Additionally, shadcn/ui copies component source code into your project, giving you full ownership and making the codebase transparent to AI coding assistants.
For a medium-complexity application (40-80 screens), expect 6-8 weeks with a dedicated frontend team. The timeline depends on the number of unique MUI components used, the complexity of custom styling overrides, and whether you are also consolidating other UI library dependencies. Simpler applications with fewer than 20 screens can be migrated in 2-3 weeks.
A rules.md file is a project-level instruction document that AI coding platforms like Claude, Cursor, and GitHub Copilot read before generating code. It specifies the project tech stack, preferred component library, styling conventions, file naming patterns, and anti-patterns to avoid. With a well-written rules.md, AI-generated code automatically follows the design system without manual developer intervention.
Yes. During the migration, both libraries run simultaneously. Tailwind CSS is configured with the important strategy to prevent class conflicts with MUI Emotion styles. Components are migrated incrementally — page by page, starting with leaf components (Button, Input) and ending with complex components (DataGrid, Autocomplete). The application remains deployable throughout.
Typical reductions range from 40-65% of total JavaScript bundle size. In our case study, the gzipped bundle dropped from 1.8 MB to 680 KB (62% reduction). The savings come from removing Emotion CSS-in-JS runtime, MUI component code, duplicate UI libraries, and unused icon bundles. Build times also improve significantly since Tailwind compiles CSS at build time rather than runtime.
Ready when you are

Let's build something great together.

A 30-minute call with a principal engineer. We'll listen, sketch, and tell you whether we're the right partner — even if the answer is no.

Talk to a strategist
Need help with your project? Let's talk.
Book a call
EmailWhatsApp