MUI to BUI Migration Skill
This skill helps migrate Backstage plugins from Material-UI (@material-ui/core, @material-ui/icons) to Backstage UI (@backstage/ui).
Prerequisites
Before starting migration:
Install the BUI package:
yarn add @backstage/uiAdd the CSS import to your root file (typically
src/index.tsor app entry point):import '@backstage/ui/css/styles.css';
Available BUI Components
Layout Components
Box- Basic layout container with CSS propertiesContainer- Centered content container with max-widthFlex- Flex layout componentFullPage- Full-page layout wrapperGrid- CSS Grid-based layout (Grid.Root,Grid.Item)
UI Components
Accordion- Collapsible content panels (Accordion,AccordionTrigger,AccordionPanel,AccordionGroup)Alert- Alert/notification banners (status,title,description)Avatar- User/entity avatarsBadge- Inline badge/label with optional icon (size,icon)Button- Action buttons (variant="primary",variant="secondary",variant="tertiary",isDisabled,destructive,loading)ButtonIcon- Icon-only buttons (icon,onPress,variant)ButtonLink- Link styled as buttonCard- Content cards (Card,CardHeader,CardBody,CardFooter)Checkbox- Checkbox inputCheckboxGroup- Grouped checkboxes with shared label (label,orientation,isRequired)DateRangePicker- Date range input field (label,value,onChange)Dialog- Modal dialogs (DialogTrigger,Dialog,DialogHeader,DialogBody,DialogFooter)FieldLabel- Form field label with description and secondary labelHeader- Page headers with breadcrumbs and tabsLink- Navigation linksList- List component (List,ListRow)Menu- Dropdown menus (MenuTrigger,Menu,MenuItem,MenuSection,MenuSeparator,SubmenuTrigger)PasswordField- Password input fieldPluginHeader- Plugin-level header with icon, title, tabs, and actionsPopover- Popover overlaysRadioGroup- Radio button groups (RadioGroup,Radio)SearchAutocomplete- Search input with autocomplete popover (SearchAutocomplete,SearchAutocompleteItem)SearchField- Search inputSelect- Dropdown select (single and multiple selection modes)Skeleton- Loading skeletonSlider- Range slider input (label,minValue,maxValue,step)Switch- Toggle switchTable- Data tables (withuseTablehook for data management)TablePagination- Standalone pagination componentTabs- Tab navigation (Tabs,TabList,Tab,TabPanel)Tag- Tag/chip component (replaces MUI Chip)TagGroup- Tag/chip groupsText- Typography component (variant,color,weight,truncate)TextField- Text input (isRequired,onChangereceives string directly)ToggleButton- Toggle buttonsToggleButtonGroup- Grouped toggle buttonsTooltip- Tooltip overlays (TooltipTrigger,Tooltip— both from@backstage/ui)VisuallyHidden- Accessibility helper
Hooks
useBreakpoint- Responsive breakpoint hookuseTable- Table data management hook (supportscomplete,offset, andcursorpagination modes)
Migration Patterns
1. Import Changes
Remove MUI imports:
// REMOVE these imports
import { Box, Typography, Tooltip, Paper } from '@material-ui/core';
import { makeStyles, Theme } from '@material-ui/core/styles';
import SomeIcon from '@material-ui/icons/SomeIcon';
Add BUI imports:
// ADD these imports
import { Box, Flex, Text, Tooltip, Card } from '@backstage/ui';
import { RiSomeIcon } from '@remixicon/react';
import styles from './MyComponent.module.css';
2. Styling: makeStyles to CSS Modules
Create a .module.css file alongside your component using BUI CSS variables.
Before (MUI makeStyles):
// MyComponent.tsx
import { makeStyles, Theme } from '@material-ui/core/styles';
const useStyles = makeStyles((theme: Theme) => ({
container: {
padding: theme.spacing(2),
backgroundColor: theme.palette.background.paper,
borderRadius: theme.shape.borderRadius,
},
title: {
marginBottom: theme.spacing(1),
color: theme.palette.text.primary,
},
listItem: {
display: 'flex',
alignItems: 'center',
},
icon: {
minWidth: 56,
color: theme.palette.text.secondary,
},
}));
function MyComponent() {
const classes = useStyles();
return (
<div className={classes.container}>
<Typography className={classes.title}>Title</Typography>
<div className={classes.listItem}>
<div className={classes.icon}>
<SomeIcon />
</div>
<span>Content</span>
</div>
</div>
);
}
After (CSS Modules with BUI variables):
/* MyComponent.module.css */
@layer components {
.container {
padding: var(--bui-space-4);
background-color: var(--bui-bg-neutral-1);
border-radius: var(--bui-radius-2);
}
.title {
margin-bottom: var(--bui-space-2);
color: var(--bui-fg-primary);
}
.listItem {
display: flex;
align-items: center;
padding: var(--bui-space-2) 0;
}
.icon {
min-width: 56px;
display: flex;
align-items: center;
justify-content: center;
color: var(--bui-fg-secondary);
}
}
// MyComponent.tsx
import { Box, Text } from '@backstage/ui';
import { RiSomeIcon } from '@remixicon/react';
import styles from './MyComponent.module.css';
function MyComponent() {
return (
<Box className={styles.container}>
<Text className={styles.title}>Title</Text>
<div className={styles.listItem}>
<div className={styles.icon}>
<RiSomeIcon size={24} />
</div>
<span>Content</span>
</div>
</Box>
);
}
3. Layout: Box with display to Flex
Before (MUI Box with display prop):
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="space-between"
>
<Box display="flex" flexDirection="row" gap={2}>
{children}
</Box>
</Box>
After (BUI Flex component):
<Flex direction="column" align="center" justify="between">
<Flex direction="row" style={{ gap: 'var(--bui-space-4)' }}>
{children}
</Flex>
</Flex>
Note: BUI Flex uses justify="between" not justify="space-between".
4. Grid Layout
Before (MUI Grid):
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
{content}
</Grid>
</Grid>
After (BUI Grid):
<Grid.Root columns={{ sm: '12' }} gap="6">
<Grid.Item colSpan={{ sm: '12', md: '6' }}>{content}</Grid.Item>
</Grid.Root>
5. Typography to Text
Before (MUI Typography):
<Typography variant="h1">Heading</Typography>
<Typography variant="h6">Subheading</Typography>
<Typography variant="body1">Body text</Typography>
<Typography variant="body2" color="textSecondary">Secondary text</Typography>
After (BUI Text):
<Text variant="title-large">Heading</Text>
<Text variant="title-small">Subheading</Text>
<Text variant="body-medium">Body text</Text>
<Text variant="body-small" color="secondary">Secondary text</Text>
Valid Text variants: title-large, title-medium, title-small, title-x-small, body-large, body-medium,
body-small, body-x-small
6. Tooltip Pattern
Before (MUI Tooltip):
import { Tooltip, Typography } from '@material-ui/core';
<Tooltip title={<Typography>Tooltip content</Typography>}>
<span>Hover me</span>
</Tooltip>;
After (BUI TooltipTrigger pattern):
import { Tooltip, TooltipTrigger, Text } from '@backstage/ui';
<TooltipTrigger>
<Text>Hover me</Text>
<Tooltip>Tooltip content</Tooltip>
</TooltipTrigger>;
7. Dialog Pattern
Before (MUI Dialog):
import { Dialog, DialogTitle, DialogActions, Button } from '@material-ui/core';
<Dialog open={isOpen}
<DialogTitle>Title</DialogTitle>
<DialogActions>
<Button
<Button color="primary">
Confirm
</Button>
</DialogActions>
</Dialog>;
After (BUI Dialog):
import {
Dialog,
DialogTrigger,
DialogHeader,
DialogFooter,
Button,
} from '@backstage/ui';
<DialogTrigger>
<Dialog
isOpen={isOpen}
isDismissable
=> {
if (!open) onClose();
}}
>
<DialogHeader>Title</DialogHeader>
<DialogFooter>
<Button variant="primary">
Confirm
</Button>
<Button variant="secondary" slot="close">
Cancel
</Button>
</DialogFooter>
</Dialog>
</DialogTrigger>;
8. Button Changes
Before (MUI Button):
<Button variant="contained" color="primary" disabled={loading}
Submit
</Button>
<IconButton disabled={!canDelete}>
<DeleteIcon />
</IconButton>
After (BUI Button):
<Button variant="primary" isDisabled={loading}
Submit
</Button>
<ButtonIcon
aria-label="delete"
isDisabled={!canDelete}
icon={<RiDeleteBinLine size={16} />}
variant="secondary"
/>
9. TextField Changes
Before (MUI TextField):
<TextField
required
name="title"
label="Title"
value={value}
=> setValue(e.target.value)}
fullWidth
/>
After (BUI TextField):
<TextField
isRequired
id="title"
label="Title"
value={value}
=> setValue(newValue)} // receives string directly!
/>
Note: BUI TextField onChange receives the string value directly, not an event object.
10. Tabs Pattern
Before (MUI Tabs):
import { Tab } from '@material-ui/core';
import { TabContext, TabList, TabPanel } from '@material-ui/lab';
<TabContext value={tab}>
<TabList
<Tab label="Tab 1" value="tab1" />
<Tab label="Tab 2" value="tab2" />
</TabList>
<TabPanel value="tab1">Content 1</TabPanel>
<TabPanel value="tab2">Content 2</TabPanel>
</TabContext>;
After (BUI Tabs):
import { Tabs, TabList, Tab, TabPanel } from '@backstage/ui';
<Tabs defaultSelectedKey="tab1">
<TabList>
<Tab id="tab1">Tab 1</Tab>
<Tab id="tab2">Tab 2</Tab>
</TabList>
<TabPanel id="tab1">Content 1</TabPanel>
<TabPanel id="tab2">Content 2</TabPanel>
</Tabs>;
11. Menu Pattern
Before (MUI Menu):
import {IconButton, Popover, MenuList, MenuItem} from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
<IconButton
<MoreVertIcon />
</IconButton>
<Popover open={open} anchorEl={anchorEl}
<MenuList>
<MenuItem
</MenuList>
</Popover>
After (BUI Menu):
import { ButtonIcon, Menu, MenuItem, MenuTrigger } from '@backstage/ui';
import { RiMore2Line } from '@remixicon/react';
<MenuTrigger>
<ButtonIcon aria-label="more" icon={<RiMore2Line />} variant="secondary" />
<Menu>
<MenuItem
</Menu>
</MenuTrigger>;
12. List to BUI List
Before (MUI List):
import { List, ListItem, ListItemIcon, ListItemText } from '@material-ui/core';
<List>
<ListItem>
<ListItemIcon>
<SomeIcon />
</ListItemIcon>
<ListItemText primary="Title" secondary="Description" />
</ListItem>
</List>;
After (BUI List):
import { List, ListRow } from '@backstage/ui';
import { RiSomeIcon } from '@remixicon/react';
<List>
<ListRow icon={<RiSomeIcon size={20} />} description="Description">
Title
</ListRow>
</List>;
Note: ListRow supports icon, description, menuItems, and customActions props.
13. Chip to Tag
Before (MUI Chip):
import { Chip } from '@material-ui/core';
<Chip label="Category" size="small" />;
After (BUI Tag):
import { Tag } from '@backstage/ui';
<Tag size="small">Category</Tag>;
14. Alert Pattern
Before (MUI Alert):
import { Alert, AlertTitle } from '@material-ui/lab';
<Alert severity="error">
<AlertTitle>Error</AlertTitle>
Something went wrong.
</Alert>;
After (BUI Alert):
import { Alert } from '@backstage/ui';
<Alert
status="danger"
icon
title="Error"
description="Something went wrong."
/>;
Status mapping: severity="error" → status="danger", severity="warning" → status="warning",
severity="info" → status="info", severity="success" → status="success".
Set icon to true for automatic status icons, or pass a custom ReactElement.
Use loading for a loading spinner, and customActions for action buttons.
15. Icons: MUI Icons to Remix Icons
Before (MUI Icons):
import CloseIcon from '@material-ui/icons/Close';
import SearchIcon from '@material-ui/icons/Search';
<CloseIcon />
<SearchIcon fontSize="small" />
After (Remix Icons):
import {RiCloseLine, RiSearchLine} from '@remixicon/react';
<RiCloseLine />
<RiSearchLine size={16} />
Common icon mappings:
| MUI Icon | Remix Icon |
|---|---|
Close |
RiCloseLine |
Search |
RiSearchLine |
Settings |
RiSettingsLine |
Add |
RiAddLine |
Delete |
RiDeleteBinLine |
Edit |
RiEditLine |
Check |
RiCheckLine |
Error |
RiErrorWarningLine |
Warning |
RiAlertLine |
Info |
RiInformationLine |
ExpandMore |
RiArrowDownSLine |
ExpandLess |
RiArrowUpSLine |
ChevronRight |
RiArrowRightSLine |
ChevronLeft |
RiArrowLeftSLine |
Menu |
RiMenuLine |
MoreVert |
RiMore2Line |
Visibility |
RiEyeLine |
VisibilityOff |
RiEyeOffLine |
NewReleases |
RiMegaphoneLine |
RecordVoiceOver |
RiMegaphoneLine |
Description |
RiFileTextLine |
Find more icons at: https://remixicon.com/
16. Paper to Card
Before (MUI Paper):
import { Paper, Typography } from '@material-ui/core';
<Paper elevation={2}>
<Typography variant="h6">Title</Typography>
<Typography>Body content</Typography>
</Paper>;
After (BUI Card):
import { Card, CardHeader, CardBody, Text } from '@backstage/ui';
<Card>
<CardHeader>Title</CardHeader>
<CardBody>
<Text>Body content</Text>
</CardBody>
</Card>;
17. Select
Before (MUI Select):
import { FormControl, InputLabel, Select, MenuItem } from '@material-ui/core';
<FormControl fullWidth>
<InputLabel>Framework</InputLabel>
<Select value={value} => setValue(e.target.value as string)}>
<MenuItem value="react">React</MenuItem>
<MenuItem value="angular">Angular</MenuItem>
</Select>
</FormControl>;
After (BUI Select):
import { Select } from '@backstage/ui';
<Select
label="Framework"
selectedKey={value}
=> setValue(key as string)}
options={[
{ value: 'react', label: 'React' },
{ value: 'angular', label: 'Angular' },
]}
/>;
Note: BUI Select accepts flat options arrays or grouped OptionSection arrays. Pass multiple for multi-select.
18. Accordion
Before (MUI Accordion):
import {
Accordion,
AccordionSummary,
AccordionDetails,
} from '@material-ui/core';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
Section title
</AccordionSummary>
<AccordionDetails>Content goes here</AccordionDetails>
</Accordion>;
After (BUI Accordion):
import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui';
<Accordion>
<AccordionTrigger title="Section title" />
<AccordionPanel>Content goes here</AccordionPanel>
</Accordion>;
Use AccordionGroup to wrap multiple Accordion items and control whether multiple panels can be open simultaneously.
19. RadioGroup
Before (MUI RadioGroup):
import {
FormControl,
FormLabel,
RadioGroup,
FormControlLabel,
Radio,
} from '@material-ui/core';
<FormControl>
<FormLabel>Frequency</FormLabel>
<RadioGroup value={value} => setValue(e.target.value)}>
<FormControlLabel value="daily" control={<Radio />} label="Daily" />
<FormControlLabel value="weekly" control={<Radio />} label="Weekly" />
</RadioGroup>
</FormControl>;
After (BUI RadioGroup):
import { RadioGroup, Radio } from '@backstage/ui';
<RadioGroup label="Frequency" value={value}
<Radio value="daily">Daily</Radio>
<Radio value="weekly">Weekly</Radio>
</RadioGroup>;
20. Badge
Before (MUI Badge):
import { Badge } from '@material-ui/core';
<Badge badgeContent={4} color="primary">
<MailIcon />
</Badge>;
After (BUI Badge):
import { Badge } from '@backstage/ui';
import { RiMailLine } from 'react-icons/ri';
<Badge>New</Badge>
<Badge size="small" icon={<RiMailLine size={12} />}>4</Badge>
Note: BUI Badge is a label-style badge (inline text with optional icon), not a notification counter overlay.
For notification counters overlaid on icons, use CSS positioning.
21. Slider
Before (MUI Slider):
import { Slider } from '@material-ui/core';
<Slider
value={value}
newValue) => setValue(newValue as number)}
min={0}
max={100}
step={10}
/>;
After (BUI Slider):
import { Slider } from '@backstage/ui';
<Slider
label="Volume"
value={value}
minValue={0}
maxValue={100}
step={10}
/>;
Note: BUI Slider onChange receives the new value directly. Use minValue/maxValue instead of min/max.
22. CheckboxGroup
Before (MUI FormGroup with Checkboxes):
import {
FormControl,
FormLabel,
FormGroup,
FormControlLabel,
Checkbox,
} from '@material-ui/core';
<FormControl>
<FormLabel>Options</FormLabel>
<FormGroup>
<FormControlLabel
control={
<Checkbox
checked={values.a}
=> handleChange('a', e.target.checked)}
/>
}
label="Option A"
/>
<FormControlLabel
control={
<Checkbox
checked={values.b}
=> handleChange('b', e.target.checked)}
/>
}
label="Option B"
/>
</FormGroup>
</FormControl>;
After (BUI CheckboxGroup):
import { CheckboxGroup, Checkbox } from '@backstage/ui';
<CheckboxGroup label="Options" value={selected}
<Checkbox value="a">Option A</Checkbox>
<Checkbox value="b">Option B</Checkbox>
</CheckboxGroup>;
CSS Variable Reference
Spacing
| MUI theme.spacing() | BUI CSS Variable |
|---|---|
theme.spacing(0.5) |
var(--bui-space-1) |
theme.spacing(1) |
var(--bui-space-2) |
theme.spacing(1.5) |
var(--bui-space-3) |
theme.spacing(2) |
var(--bui-space-4) |
theme.spacing(3) |
var(--bui-space-6) |
theme.spacing(4) |
var(--bui-space-8) |
Colors
| MUI theme.palette | BUI CSS Variable |
|---|---|
text.primary |
var(--bui-fg-primary) |
text.secondary |
var(--bui-fg-secondary) |
background.paper |
var(--bui-bg-neutral-1) |
background.default |
var(--bui-bg-app) |
primary.main |
var(--bui-bg-solid) or var(--bui-ring) |
error.main |
var(--bui-fg-danger) |
action.hover |
var(--bui-bg-neutral-1-hover) |
divider |
var(--bui-border-1) |
Typography
| Property | BUI CSS Variable |
|---|---|
| Font family | var(--bui-font-regular) |
| Font size small | var(--bui-font-size-1) |
| Font size medium | var(--bui-font-size-2) |
| Font size large | var(--bui-font-size-3) |
| Font weight regular | var(--bui-font-weight-regular) |
| Font weight bold | var(--bui-font-weight-bold) |
Other
| Property | BUI CSS Variable |
|---|---|
| Border radius small | var(--bui-radius-2) |
| Border radius medium | var(--bui-radius-3) |
| Border radius full | var(--bui-radius-full) |
| Link color | var(--bui-fg-info) |
Known Limitations
Some Backstage APIs still require MUI-compatible icon types:
- PageBlueprint (
@backstage/frontend-plugin-api): Theiconparam on page extensions expects anIconElement. MUI icon components can still be used via<Icon fontSize="inherit" />. - Timeline (
@material-ui/lab): No BUI equivalent exists.
For these cases, keep using MUI components.
Migration Checklist
When migrating a plugin:
- Add
@backstage/uidependency - Add
@remixicon/reactdependency (if using icons) - Add CSS import to root file
- Remove
@material-ui/coreimports (except components with no BUI equivalent) - Remove
@material-ui/iconsimports - Remove
@material-ui/labimports (Alert, Pagination now in BUI) - Remove
makeStylesand related imports - Create
.module.cssfiles for component styles - Replace
TypographywithText - Replace
Box display="flex"withFlex - Replace
Grid container/itemwithGrid.Root/Grid.Item - Replace
PaperwithCard - Replace MUI
Dialogwith BUIDialogTriggerpattern - Replace MUI
Tooltipwith BUITooltipTriggerpattern (both from@backstage/ui) - Replace MUI
Tabswith BUITabs - Replace MUI
Menu/Popoverwith BUIMenuTriggerpattern - Replace
ChipwithTag - Replace
IconButtonwithButtonIcon - Replace MUI
Alertwith BUIAlert - Replace MUI
Listwith BUIListandListRow - Replace MUI
Select/FormControlwith BUISelect - Replace MUI
Accordionwith BUIAccordion/AccordionTrigger/AccordionPanel - Replace MUI
RadioGroup/FormControlLabelwith BUIRadioGroup/Radio - Replace MUI
FormGroupwith BUICheckboxGroup - Replace MUI
Sliderwith BUISlider - Update
Buttonprops (disabled→isDisabled,variant="contained"→variant="primary") - Update
TextFieldprops (required→isRequired,onChangesignature) - Replace MUI icons with Remix icons
- Run
yarn tscto check for type errors - Run the project's build command (e.g.
yarn build,yarn build:all, oryarn workspace <pkg> build) to verify build - Run
yarn lintto check for missing dependencies - Test component rendering and functionality
Reference
- BUI Documentation: https://ui.backstage.io
- Remix Icons: https://remixicon.com/
- Example Migration PR: https://github.com/backstage/backstage/pull/31631