Component Audit — shadcn/ui → MUI Core, by Atomic layer
Audit of all 62 components in src/components/ui/, each placed in its Atomic Design layer, mapped to its MUI Core (free/MIT) target, with a deviation rating (how far today's look/behavior sits from stock MUI) and an action.
Usage = distinct files importing the component from outside src/components/ui/ (sibling ui-internal imports excluded, so button/label/textarea reach slightly higher in reality).
Action legend
| Action | Meaning |
|---|---|
| MAP | Use the MUI component directly; the theme carries the brand. Thin/no wrapper. |
| WRAP | Owned wrapper around a MUI component adding brand variants/props. |
| BUILD | Custom composite we build ourselves (MUI primitives inside). No Pro. |
| KEEP-LIB | Keep the current third-party lib — not MUI's domain; MUI has no equal or better. |
| DELEGATE | Data-rich table → AG Grid; chart → recharts (owner-confirmed). |
| CONSOLIDATE | Duplicate/parallel implementations merged into one owned target. |
Deviation ratings
- Low — stock MUI ≈ current look; theme defaults suffice.
- Med — needs
styleOverridesand/or a custom variant/prop. - High — signature brand traits (asymmetric radii, custom shadows, extra variants, Tailwind-class or global-CSS coupling) that require real theme engineering.
Atoms
| Component | Uses | MUI Core target | Deviation | Action | Notes |
|---|---|---|---|---|---|
| button | 167 | Button (+ IconButton) | High | WRAP | 9 variants (hero, hero-outline, success beyond MUI's 3) + xl size + asymmetric radius + --tf-btn-* shadows + hover-lift + disabled ripple. Add custom variants + module augmentation; map contained/outlined/text→default/outline/link. Highest-leverage single migration. |
| badge | 84 | Chip (or themed Box) | Med | WRAP | 4 variants. Chip is closest; may keep a lightweight themed span for the non-interactive count badges. |
| label | 50 | FormLabel / InputLabel | Low | MAP | Error state (text-destructive) comes from the field's error prop. |
| input | 43 | TextField (or OutlinedInput) | Med | WRAP | Validation is driven by aria-invalid/data-valid today → MUI error + color="success" + helperText. See form-layer redesign in the architecture spec. |
| textarea | 26 | TextField multiline | Low | MAP | Same field wrapper as input. |
| skeleton | 25 | Skeleton | Low | MAP | — |
| typography (11 exports) | 15 | Typography (native h1–h6/body/caption variants) | Med | WRAP → thin <Text> | Wrapped onto MUI's native variant layer (no custom variants): Futura headings, Poppins body+labels, no Roboto, no mono. One <Text> (brand-name→variant map) replaces 11 components. Full spec: typography-system.md. |
| icon | 6 | @mui/icons-material | Med | WRAP | Owner-confirmed: adopt MUI's out-of-the-box Material icons (free/MIT, tree-shakeable named imports). Keep the a11y <Icon> wrapper (aria-hidden vs role=img, ui/micro sizes) but back it with MUI icons. Migration cost: lucide-react is imported directly across many files → map lucide names → MUI icon names, then remove lucide-react. Restraint rule: icons are functional affordances only — do not decorate every heading/label with an icon. |
| checkbox | 12 | Checkbox | Low | MAP | — |
| separator | 10 | Divider | Low | MAP | — |
| progress | 7 | LinearProgress / CircularProgress | Low | MAP | — |
| switch | 6 | Switch | Low | MAP | — |
| avatar | 2 | Avatar | Low | MAP | — |
| scroll-area | 15 | (native + theme) | Low | MAP | MUI has no ScrollArea; use native overflow + a themed scrollbar. Low effort, high reach. |
| aspect-ratio | 2 | Box (aspect-ratio CSS) | Low | MAP | — |
| slider | 1 | Slider | Low | MAP | — |
| radio-group | 1 | Radio + RadioGroup | Low | MAP | — |
| toggle | 0 | ToggleButton | Low | MAP | — |
| input-otp (slot) | (3 via molecule) | (keep input-otp) | Low | KEEP-LIB | MUI has no OTP field; keep input-otp, theme the slots. |
Molecules
| Component | Uses | MUI Core target | Deviation | Action | Notes |
|---|---|---|---|---|---|
| card (+ header/title/desc/content/footer) | 68 | Card / Paper + CardHeader/Content/Actions | High | WRAP | Bespoke .tf-card: 40px asymmetric radius, inset-glow shadows, Futura headings, compact/muted variants. Critical: the global Tailwind auto-retrofit in index.css must be ported to MuiPaper/MuiCard styleOverrides before it is deleted, or MUI surfaces render unbranded. |
| form (FormItem/Field/Label/Control/Message) | 0 | TextField model + RHF Controller | High | BUILD | Radix Slot (asChild) injection is incompatible with MUI inputs. Rebuild as owned RHFTextField/RHFSelect… adapters using useController. 0 external importers today → clean to redesign. |
| multi-select | 11 | Autocomplete multiple | Med | BUILD→WRAP | MUI Autocomplete gives searchable multi + chips nearly for free — a simplification, not a gap. Wrap to preserve the current props (options/selected/onChange/emptyMessage/aria-invalid). |
| breadcrumb | 12 | Breadcrumbs | Low | MAP | — |
| alert-dialog | 12 | Dialog + actions | Low | WRAP | Uses buttonVariants today → owned buttons. |
| popover | 10 | Popover / Menu | Low | MAP | — |
| tabs | 9 | Tabs + Tab | Low | MAP | — |
| responsive-tabs | 8 | Tabs + Select (mobile) | Med | BUILD | Custom: Radix tabs on desktop, native dropdown on mobile via useIsMobile. Rebuild as owned molecule on MUI Tabs/Select. |
| confirm-dialog | 5 | Dialog | Low | BUILD | Owned molecule on MUI Dialog (title/consequence/destructive/loading). |
| validated-field | 4 | TextField error model | Med | CONSOLIDATE | Subsumed by the RHFTextField field molecule + error/helperText. Retire as its own thing. |
| dropdown-menu | 4 | Menu + MenuItem | Low | MAP | Radio/checkbox items → MenuItem + Checkbox/Radio. |
| accordion | 3 | Accordion | Low | MAP | — |
| toggle-group | 3 | ToggleButtonGroup | Low | MAP | — |
| input-otp | 3 | (keep input-otp) | Low | KEEP-LIB | See atom note. |
| tooltip | 2 | Tooltip | Low | MAP | — |
| alert | 1 | Alert | Low | MAP | 2 variants → severity. |
| char-count-textarea | 1 | TextField + helperText counter | Low | BUILD | Thin owned wrapper (maxLength + live aria-live count). |
| pagination | 0 | Pagination | Low | MAP | Note: AG Grid has its own paging for tables. |
| hover-card | 0 | Popover (hover) | Low | MAP | — |
| collapsible | 0 | Collapse | Low | MAP | — |
| save-status / SaveStatus / AutosaveStatus | 7 + 1 + 0 | (custom, no MUI primitive) | Med | CONSOLIDATE→BUILD | Three overlapping implementations with different APIs. Merge into one owned SaveStatus molecule (idle/dirty/saving/saved/error, relative time, retry, aria-live). Cleanup win. |
Organisms
| Component | Uses | MUI Core target | Deviation | Action | Notes |
|---|---|---|---|---|---|
| dialog | 41 | Dialog | Low | MAP | Highest-use organism; theme the Paper radius/shadow to match. |
| sheet | 14 | Drawer | Med | BUILD | Custom resizable side + localStorage persistence (tf:sheet-size:). Rebuild resize/persist logic over MUI Drawer. |
| sidebar | 3 | Drawer + List primitives | High | BUILD | Large custom composite (context provider, mobile Sheet, sidebarMenuButtonVariants). No MUI equivalent — rebuild as owned organism. Migrate late (shared shell). |
| command | 5 | (keep cmdk) | Low | KEEP-LIB | MUI has no command palette. Keep cmdk; theme its surface. |
| table | 4 | AG Grid | — | DELEGATE | Owner-confirmed: AG Grid for all data-rich tables. The shadcn table.tsx remains only for trivial static layout tables (or migrate those to a themed Table). |
| chart | 0 | recharts | — | DELEGATE | Keep recharts (the chart.tsx wrapper already wraps it). No MUI charts. |
| toast / toaster / use-toast + sonner | 1 + 1 + 2 | Snackbar + Alert | Med | CONSOLIDATE | Two toast systems coexist (Radix toast trio + sonner). Consolidate to one owned Snackbar API during migration. |
| calendar | 1 | (keep react-day-picker) | Low | KEEP-LIB | Keep react-day-picker (MUI date pickers are MUI X; basic ones are free but no reason to churn). |
| navigation-menu | 0 | Menu/Tabs/custom | Low | MAP/BUILD | Low use; rebuild only if a consumer appears. |
| menubar | 0 | Menu composition | Low | MAP | Unused. |
| context-menu | 0 | Menu (context) | Low | MAP | Unused. |
| drawer (vaul) | 0 | Drawer (SwipeableDrawer) | Low | KEEP-LIB/MAP | Unused; fold into the sheet rebuild. |
| carousel | 0 | (keep embla) | Low | KEEP-LIB | MUI has no carousel. |
| resizable | 0 | (keep react-resizable-panels) | Low | KEEP-LIB | Unused; keep lib if needed. |
Templates & Pages
Not in src/components/ui/ — these are the layers above the library:
- Templates: app shells / route layouts (
src/layouts/**, page scaffolds). They compose organisms (Sidebar, nav, Dialog hosts, Toaster) and get migrated after the organisms they use exist in TFDS. - Pages:
src/pages/**andsrc/features/**(231 files consuming the library). These are migrated feature-area by feature-area in the phased rollout; auth pages last (per06-auth-flow-lockdown).
Utilities (in the folder, not atomic UI)
Framework-agnostic; no MUI equivalent needed, keep as-is: OptimizedImage.tsx, responsive-image.tsx (native <picture>), VirtualList.tsx (@tanstack/react-virtual), use-toast.ts (folded into the toast consolidation).
Summary — what MUI Core covers vs what we build
- Direct MAP (theme carries the brand): ~28 components. The bulk of primitives — dialog, select, checkbox, switch, tabs, popover, menu, breadcrumb, alert, skeleton, divider, progress, avatar, slider, etc. — are 1:1 with MUI Core.
- WRAP (owned wrapper, brand variants): ~5. button, card, input/textarea field, badge, alert-dialog.
- BUILD ourselves (no Pro, no fork): ~7 composites.
<Text>(typography), form field adapters (RHFTextField…), responsive-tabs, confirm-dialog, char-count-textarea, sheet (resizable), sidebar. Plus consolidations: 3 save-status → 1, 2 toast systems → 1, multi-select → Autocomplete. - KEEP-LIB (not MUI's domain): ~5. cmdk (command), embla (carousel), react-day-picker (calendar), input-otp, react-resizable-panels.
- ADOPT:
@mui/icons-material(out-of-the-box Material icons) — replaceslucide-react, which is then removed. Used purposefully, not decoratively. - DELEGATE: AG Grid (tables), recharts (charts).
- Paid MUI X needed: none. Every gap is buildable in the owned layer at $0.
Deviation hot-spots to prove first (Phase 0/1): button (167 uses, 9 variants + asymmetric radius), card (68 uses, asymmetric radius + glow + the Tailwind auto-retrofit landmine), the form/field model (Radix Slot → MUI error model), and typography (bespoke scale). Nail these four and the theme proves the whole approach.