Skip to content

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 ​

ActionMeaning
MAPUse the MUI component directly; the theme carries the brand. Thin/no wrapper.
WRAPOwned wrapper around a MUI component adding brand variants/props.
BUILDCustom composite we build ourselves (MUI primitives inside). No Pro.
KEEP-LIBKeep the current third-party lib — not MUI's domain; MUI has no equal or better.
DELEGATEData-rich table → AG Grid; chart → recharts (owner-confirmed).
CONSOLIDATEDuplicate/parallel implementations merged into one owned target.

Deviation ratings ​

  • Low — stock MUI ≈ current look; theme defaults suffice.
  • Med — needs styleOverrides and/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 ​

ComponentUsesMUI Core targetDeviationActionNotes
button167Button (+ IconButton)HighWRAP9 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.
badge84Chip (or themed Box)MedWRAP4 variants. Chip is closest; may keep a lightweight themed span for the non-interactive count badges.
label50FormLabel / InputLabelLowMAPError state (text-destructive) comes from the field's error prop.
input43TextField (or OutlinedInput)MedWRAPValidation is driven by aria-invalid/data-valid today → MUI error + color="success" + helperText. See form-layer redesign in the architecture spec.
textarea26TextField multilineLowMAPSame field wrapper as input.
skeleton25SkeletonLowMAP—
typography (11 exports)15Typography (native h1–h6/body/caption variants)MedWRAP → 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.
icon6@mui/icons-materialMedWRAPOwner-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.
checkbox12CheckboxLowMAP—
separator10DividerLowMAP—
progress7LinearProgress / CircularProgressLowMAP—
switch6SwitchLowMAP—
avatar2AvatarLowMAP—
scroll-area15(native + theme)LowMAPMUI has no ScrollArea; use native overflow + a themed scrollbar. Low effort, high reach.
aspect-ratio2Box (aspect-ratio CSS)LowMAP—
slider1SliderLowMAP—
radio-group1Radio + RadioGroupLowMAP—
toggle0ToggleButtonLowMAP—
input-otp (slot)(3 via molecule)(keep input-otp)LowKEEP-LIBMUI has no OTP field; keep input-otp, theme the slots.

Molecules ​

ComponentUsesMUI Core targetDeviationActionNotes
card (+ header/title/desc/content/footer)68Card / Paper + CardHeader/Content/ActionsHighWRAPBespoke .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)0TextField model + RHF ControllerHighBUILDRadix Slot (asChild) injection is incompatible with MUI inputs. Rebuild as owned RHFTextField/RHFSelect… adapters using useController. 0 external importers today → clean to redesign.
multi-select11Autocomplete multipleMedBUILD→WRAPMUI 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).
breadcrumb12BreadcrumbsLowMAP—
alert-dialog12Dialog + actionsLowWRAPUses buttonVariants today → owned buttons.
popover10Popover / MenuLowMAP—
tabs9Tabs + TabLowMAP—
responsive-tabs8Tabs + Select (mobile)MedBUILDCustom: Radix tabs on desktop, native dropdown on mobile via useIsMobile. Rebuild as owned molecule on MUI Tabs/Select.
confirm-dialog5DialogLowBUILDOwned molecule on MUI Dialog (title/consequence/destructive/loading).
validated-field4TextField error modelMedCONSOLIDATESubsumed by the RHFTextField field molecule + error/helperText. Retire as its own thing.
dropdown-menu4Menu + MenuItemLowMAPRadio/checkbox items → MenuItem + Checkbox/Radio.
accordion3AccordionLowMAP—
toggle-group3ToggleButtonGroupLowMAP—
input-otp3(keep input-otp)LowKEEP-LIBSee atom note.
tooltip2TooltipLowMAP—
alert1AlertLowMAP2 variants → severity.
char-count-textarea1TextField + helperText counterLowBUILDThin owned wrapper (maxLength + live aria-live count).
pagination0PaginationLowMAPNote: AG Grid has its own paging for tables.
hover-card0Popover (hover)LowMAP—
collapsible0CollapseLowMAP—
save-status / SaveStatus / AutosaveStatus7 + 1 + 0(custom, no MUI primitive)MedCONSOLIDATE→BUILDThree overlapping implementations with different APIs. Merge into one owned SaveStatus molecule (idle/dirty/saving/saved/error, relative time, retry, aria-live). Cleanup win.

Organisms ​

ComponentUsesMUI Core targetDeviationActionNotes
dialog41DialogLowMAPHighest-use organism; theme the Paper radius/shadow to match.
sheet14DrawerMedBUILDCustom resizable side + localStorage persistence (tf:sheet-size:). Rebuild resize/persist logic over MUI Drawer.
sidebar3Drawer + List primitivesHighBUILDLarge custom composite (context provider, mobile Sheet, sidebarMenuButtonVariants). No MUI equivalent — rebuild as owned organism. Migrate late (shared shell).
command5(keep cmdk)LowKEEP-LIBMUI has no command palette. Keep cmdk; theme its surface.
table4AG Grid—DELEGATEOwner-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).
chart0recharts—DELEGATEKeep recharts (the chart.tsx wrapper already wraps it). No MUI charts.
toast / toaster / use-toast + sonner1 + 1 + 2Snackbar + AlertMedCONSOLIDATETwo toast systems coexist (Radix toast trio + sonner). Consolidate to one owned Snackbar API during migration.
calendar1(keep react-day-picker)LowKEEP-LIBKeep react-day-picker (MUI date pickers are MUI X; basic ones are free but no reason to churn).
navigation-menu0Menu/Tabs/customLowMAP/BUILDLow use; rebuild only if a consumer appears.
menubar0Menu compositionLowMAPUnused.
context-menu0Menu (context)LowMAPUnused.
drawer (vaul)0Drawer (SwipeableDrawer)LowKEEP-LIB/MAPUnused; fold into the sheet rebuild.
carousel0(keep embla)LowKEEP-LIBMUI has no carousel.
resizable0(keep react-resizable-panels)LowKEEP-LIBUnused; 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/** and src/features/** (231 files consuming the library). These are migrated feature-area by feature-area in the phased rollout; auth pages last (per 06-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) — replaces lucide-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.

Built on MUI Core (MIT). Part of the Tech Fleet Network monorepo.