name: mui-to-bui-migration description: Migrate Backstage plugins from Material-UI (MUI) to Backstage UI (BUI). Use this skill when migrating components, updating imports, replacing styling patterns, or converting MUI components to their BUI equivalents.
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-widthGrid- CSS Grid-based layout (Grid.Root,Grid.Item)Flex- Flexbox layout component
UI Components
Accordion- Collapsible content panelsAvatar- User/entity avatarsButton- Primary action buttons (variant="primary",variant="secondary",isDisabled)ButtonIcon- Icon-only buttons (icon,onPress,variant)ButtonLink- Link styled as buttonCard- Content cards (Card,CardHeader,CardBody,CardFooter)Checkbox- Checkbox inputDialog- Modal dialogs (DialogTrigger,Dialog,DialogHeader,DialogBody,DialogFooter)Header- Page headersHeaderPage- Full page header componentLink- Navigation linksMenu- Dropdown menus (MenuTrigger,Menu,MenuItem)PasswordField- Password input fieldPopover- Popover overlaysRadioGroup- Radio button groupsSearchField- Search inputSelect- Dropdown selectSkeleton- Loading skeletonSwitch- Toggle switchTable- Data tablesTabs- Tab navigation (Tabs,TabList,Tab,TabPanel)Tag- Tag/chip component (replaces MUI Chip)TagGroup- Tag/chip groupsText- Typography component (variant,color)TextField- Text input (isRequired,onChangereceives string directly)ToggleButton- Toggle buttonsToggleButtonGroup- Grouped toggle buttonsTooltip- Tooltip overlays (used with TooltipTrigger from react-aria-components)VisuallyHidden- Accessibility helper
Hooks
useBreakpoint- Responsive breakpoint hook
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-surface-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, Text } from '@backstage/ui';
import { TooltipTrigger } from 'react-aria-components';
<TooltipTrigger>
<Text>Hover me</Text>
<Tooltip>Tooltip content</Tooltip>
</TooltipTrigger>;
Note: Add react-aria-components to your dependencies.
7. Dialog Pattern
Before (MUI Dialog):
import { Dialog, DialogTitle, DialogActions, Button } from '@material-ui/core';
<Dialog open={isOpen} onClose={onClose}>
<DialogTitle>Title</DialogTitle>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<Button onClick={onConfirm} color="primary">Confirm</Button>
</DialogActions>
</Dialog>
After (BUI Dialog):
import { Dialog, DialogTrigger, DialogHeader, DialogFooter, Button } from '@backstage/ui';
<DialogTrigger>
<Dialog isOpen={isOpen} isDismissable onOpenChange={(open) => { if (!open) onClose(); }}>
<DialogHeader>Title</DialogHeader>
<DialogFooter>
<Button onClick={onConfirm} variant="primary">Confirm</Button>
<Button onClick={onClose} variant="secondary" slot="close">Cancel</Button>
</DialogFooter>
</Dialog>
</DialogTrigger>
8. Button Changes
Before (MUI Button):
<Button variant="contained" color="primary" disabled={loading} onClick={handleClick}>
Submit
</Button>
<IconButton onClick={handleDelete} disabled={!canDelete}>
<DeleteIcon />
</IconButton>
After (BUI Button):
<Button variant="primary" isDisabled={loading} onClick={handleClick}>
Submit
</Button>
<ButtonIcon
aria-label="delete"
isDisabled={!canDelete}
onPress={handleDelete}
icon={<RiDeleteBinLine size={16} />}
variant="secondary"
/>
9. TextField Changes
Before (MUI TextField):
<TextField
required
name="title"
label="Title"
value={value}
onChange={(e) => setValue(e.target.value)}
fullWidth
/>
After (BUI TextField):
<TextField
isRequired
id="title"
label="Title"
value={value}
onChange={(newValue) => 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 onChange={handleChange}>
<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 onClick={handleOpen}><MoreVertIcon /></IconButton>
<Popover open={open} anchorEl={anchorEl} onClose={handleClose}>
<MenuList>
<MenuItem onClick={handleAction}>Action</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 onAction={handleAction}>Action</MenuItem>
</Menu>
</MenuTrigger>
12. List to HTML with CSS Modules
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 (HTML list with BUI and CSS Modules):
/* MyList.module.css */
@layer components {
.list {
list-style: none;
padding: 0;
margin: 0;
}
.listItem {
display: flex;
align-items: flex-start;
padding: var(--bui-space-2) 0;
}
.listItemIcon {
min-width: 36px;
display: flex;
align-items: center;
color: var(--bui-fg-primary);
}
}
import { Flex, Text } from '@backstage/ui';
import { RiSomeIcon } from '@remixicon/react';
import styles from './MyList.module.css';
<ul className={styles.list}>
<li className={styles.listItem}>
<div className={styles.listItemIcon}><RiSomeIcon size={20} /></div>
<Flex direction="column">
<Text>Title</Text>
<Text variant="body-small" color="secondary">Description</Text>
</Flex>
</li>
</ul>
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. 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/
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-surface-1) |
background.default |
var(--bui-bg-surface-0) |
primary.main |
var(--bui-bg-solid) or var(--bui-ring) |
error.main |
var(--bui-fg-danger) |
action.hover |
var(--bui-bg-hover) |
divider |
var(--bui-border) |
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-link) |
Known Limitations
Some Backstage APIs still require MUI-compatible icon types:
- NavItemBlueprint (
@backstage/frontend-plugin-api): Theiconprop expects MUIIconComponenttype. Remix icons are not type-compatible. - Timeline (
@material-ui/lab): No BUI equivalent exists. - Pagination (
@material-ui/lab): No BUI equivalent exists. - Alert (
@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
react-aria-componentsdependency (if using Tooltip) - Add CSS import to root file
- Remove
@material-ui/coreimports (except components with no BUI equivalent) - Remove
@material-ui/iconsimports - 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 - Replace MUI
Tabswith BUITabs - Replace MUI
Menuwith BUIMenuTriggerpattern - Replace
ChipwithTag - Replace
IconButtonwithButtonIcon - 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
yarn buildto 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