mui-to-bui-migration
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/ui -
Add 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 avatarsButton- 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 inputDialog- 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 skeletonSwitch- 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}
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 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/
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:
- NavItemBlueprint (
@backstage/frontend-plugin-api): Theiconprop expects MUIIconComponenttype. Remix icons are not type-compatible. - 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
Menuwith BUIMenuTriggerpattern - Replace
ChipwithTag - Replace
IconButtonwithButtonIcon - Replace MUI
Alertwith BUIAlert - Replace MUI
Listwith BUIListandListRow - 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