Files
metabuilder/docs/UI_MIGRATION.md
JohnDoe6345789 a2932725c3 feat: add custom Table component and refactor Select component to use MUI
- Introduced a new Table component with TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, and TableCaption for better UI consistency.
- Refactored Select component to utilize MUI's Select, MenuItem, and FormControl for improved accessibility and styling.
- Created a centralized MUI theme with design tokens, typography, spacing, and component overrides for light and dark modes.
2025-12-25 16:54:22 +00:00

9.4 KiB

UI Migration Guide: From Radix UI + Tailwind to Material-UI + SASS

⚠️ IMPORTANT: Project UI Standards

This project does NOT use:

  • Radix UI (removed)
  • Tailwind CSS (removed)
  • inline className styling with utility classes

This project DOES use:

  • Material-UI (MUI) for all UI components
  • SASS/SCSS for custom styling
  • MUI's theme system for consistent design

Overview

All UI components have been migrated from Radix UI + Tailwind to Material-UI (MUI) with SASS. This document explains how to work with the new system.

Quick Reference: Component Mapping

Radix UI → Material-UI

Old (Radix UI) New (Material-UI)
<AlertDialog> <Dialog> with <DialogTitle> and <DialogActions>
<Accordion> <Accordion> with <AccordionSummary>
<Avatar> <Avatar>
<Checkbox> <Checkbox> with <FormControlLabel>
<Dialog> <Dialog> with <DialogTitle>, <DialogContent>
<DropdownMenu> <Menu> with <MenuItem>
<HoverCard> <Popover> with hover trigger
<Label> <FormLabel> or <InputLabel>
<Popover> <Popover>
<Progress> <LinearProgress> or <CircularProgress>
<RadioGroup> <RadioGroup> with <FormControlLabel>
<ScrollArea> Native overflow or <Box> with sx
<Select> <Select> with <MenuItem>
<Separator> <Divider>
<Sheet> <Drawer>
<Slider> <Slider>
<Switch> <Switch> with <FormControlLabel>
<Tabs> <Tabs> with <Tab>
<Toast> <Snackbar> with <Alert>
<Toggle> <ToggleButton>
<Tooltip> <Tooltip>

Tailwind Classes → MUI/SASS

Old (Tailwind) New (MUI/SASS)
className="flex items-center" sx={{ display: 'flex', alignItems: 'center' }}
className="grid grid-cols-2 gap-4" sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 2 }}
className="text-lg font-bold" <Typography variant="h6" fontWeight="bold">
className="bg-primary text-white" sx={{ bgcolor: 'primary.main', color: 'primary.contrastText' }}
className="p-4 m-2" sx={{ p: 2, m: 1 }} (MUI uses 8px spacing units)
className="rounded-lg shadow" <Paper elevation={2} sx={{ borderRadius: 2 }}>
Custom classes Create .module.scss files and import

Using Material-UI Components

Basic Button Example

import { Button } from '@mui/material'
import AddIcon from '@mui/icons-material/Add'

// Old (Radix + Tailwind):
<button className="bg-blue-500 text-white px-4 py-2 rounded">
  Click me
</button>

// New (MUI):
<Button variant="contained" color="primary" startIcon={<AddIcon />}>
  Click me
</Button>

Dialog Example

import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material'

// Old (Radix):
<AlertDialog open={open} onOpenChange={setOpen}>
  <AlertDialogTrigger>Open</AlertDialogTrigger>
  <AlertDialogContent>
    <AlertDialogHeader>Title</AlertDialogHeader>
    <AlertDialogDescription>Content</AlertDialogDescription>
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogAction>OK</AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>

// New (MUI):
<>
  <Button onClick={() => setOpen(true)}>Open</Button>
  <Dialog open={open} onClose={() => setOpen(false)}>
    <DialogTitle>Title</DialogTitle>
    <DialogContent>
      Content
    </DialogContent>
    <DialogActions>
      <Button onClick={() => setOpen(false)}>Cancel</Button>
      <Button onClick={handleOk} variant="contained">OK</Button>
    </DialogActions>
  </Dialog>
</>

Form with Input

import { TextField, FormControl, FormLabel } from '@mui/material'

// Old (Radix + Tailwind):
<div className="space-y-2">
  <Label htmlFor="email">Email</Label>
  <Input id="email" type="email" className="w-full" />
</div>

// New (MUI):
<TextField
  fullWidth
  label="Email"
  type="email"
  id="email"
  variant="outlined"
/>

Icons

import { Add, Delete, Edit, Settings } from '@mui/icons-material'

// Old:
import { Plus, Trash, Edit, Settings } from 'lucide-react'
// or
import { PlusIcon } from '@heroicons/react/24/outline'

// New:
<Add />
<Delete />
<Edit />
<Settings />

Using SASS for Custom Styling

Module SCSS Files

Create component-specific SCSS files using CSS modules:

// UserCard.module.scss
.userCard {
  padding: 16px;
  border-radius: 12px;
  background: var(--mui-palette-background-paper);
  
  .header {
    display: flex;
    align-items: center;
    gap: 12px;
  }
  
  .avatar {
    width: 48px;
    height: 48px;
  }
}
// UserCard.tsx
import styles from './UserCard.module.scss'
import { Card } from '@mui/material'

export function UserCard() {
  return (
    <Card className={styles.userCard}>
      <div className={styles.header}>
        {/* content */}
      </div>
    </Card>
  )
}

Using Theme Variables in SASS

Access MUI theme values in SCSS:

.myComponent {
  // Colors from theme
  color: var(--mui-palette-text-primary);
  background: var(--mui-palette-background-default);
  
  // Spacing (8px base unit)
  padding: var(--mui-spacing-2); // 16px
  margin: var(--mui-spacing-3);  // 24px
  
  // Typography
  font-family: var(--mui-typography-fontFamily);
}

Using MUI's sx Prop

The sx prop is MUI's shorthand for inline styling with theme access:

import { Box, Typography } from '@mui/material'

// Responsive design
<Box
  sx={{
    display: 'flex',
    flexDirection: { xs: 'column', md: 'row' },
    gap: 2,
    p: 3,
    bgcolor: 'background.paper',
    borderRadius: 2,
  }}
>
  <Typography variant="h6" color="primary.main">
    Title
  </Typography>
</Box>

Spacing System

MUI uses an 8px base spacing unit:

sx={{
  p: 1,   // padding: 8px
  p: 2,   // padding: 16px
  p: 3,   // padding: 24px
  pt: 2,  // padding-top: 16px
  px: 3,  // padding-left & padding-right: 24px
  m: 2,   // margin: 16px
  gap: 2, // gap: 16px
}}

Theme Customization

The theme is defined in mui-theme.ts:

import { useTheme } from '@/app/providers'

function MyComponent() {
  const { mode, setMode, toggleTheme } = useTheme()
  
  return (
    <Button onClick={toggleTheme}>
      Current mode: {mode}
    </Button>
  )
}

Accessing Theme in Components

import { useTheme } from '@mui/material/styles'

function MyComponent() {
  const theme = useTheme()
  
  return (
    <Box sx={{ 
      color: theme.palette.primary.main,
      [theme.breakpoints.down('md')]: {
        fontSize: '0.875rem',
      },
    }}>
      Content
    </Box>
  )
}

Data Display Components

Tables

import { 
  Table, 
  TableBody, 
  TableCell, 
  TableHead, 
  TableRow,
  Paper 
} from '@mui/material'

<Paper>
  <Table>
    <TableHead>
      <TableRow>
        <TableCell>Name</TableCell>
        <TableCell>Email</TableCell>
      </TableRow>
    </TableHead>
    <TableBody>
      {users.map(user => (
        <TableRow key={user.id}>
          <TableCell>{user.name}</TableCell>
          <TableCell>{user.email}</TableCell>
        </TableRow>
      ))}
    </TableBody>
  </Table>
</Paper>

For advanced features, use @mui/x-data-grid:

import { DataGrid } from '@mui/x-data-grid'

const columns = [
  { field: 'id', headerName: 'ID', width: 90 },
  { field: 'name', headerName: 'Name', width: 150 },
  { field: 'email', headerName: 'Email', width: 200 },
]

<DataGrid
  rows={users}
  columns={columns}
  pageSize={10}
  checkboxSelection
/>

Common Patterns

Loading States

import { CircularProgress, Box } from '@mui/material'

{isLoading ? (
  <Box display="flex" justifyContent="center" p={3}>
    <CircularProgress />
  </Box>
) : (
  <Content />
)}

Error Display

import { Alert, AlertTitle } from '@mui/material'

<Alert severity="error">
  <AlertTitle>Error</AlertTitle>
  {errorMessage}
</Alert>

Cards

import { Card, CardHeader, CardContent, CardActions, IconButton } from '@mui/material'
import { MoreVert } from '@mui/icons-material'

<Card>
  <CardHeader
    title="Card Title"
    subheader="Subtitle"
    action={
      <IconButton>
        <MoreVert />
      </IconButton>
    }
  />
  <CardContent>
    Content goes here
  </CardContent>
  <CardActions>
    <Button>Action</Button>
  </CardActions>
</Card>

Migration Checklist

When migrating a component:

  • Remove all Radix UI imports
  • Remove all Tailwind className props with utility classes
  • Replace with appropriate MUI components
  • Use sx prop for simple inline styles
  • Create .module.scss for complex custom styling
  • Update icon imports to @mui/icons-material
  • Test responsive behavior
  • Verify theme consistency (light/dark mode)
  • Update tests to work with MUI components

Resources

Need Help?

  1. Check mui-theme.ts for theme configuration
  2. Look at existing migrated components for patterns
  3. Use MUI's comprehensive documentation
  4. Create .module.scss files for complex component-specific styles