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/ui
Add the CSS import to your root file (typically src/index.ts or app entry point):
import '@backstage/ui/css/styles.css' ;
Available BUI Components
Layout Components
Box - Basic layout container with CSS properties
Container - Centered content container with max-width
Flex - Flex layout component
FullPage - Full-page layout wrapper
Grid - 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 avatars
Badge - 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 button
Card - Content cards (Card, CardHeader, CardBody, CardFooter)
Checkbox - Checkbox input
CheckboxGroup - 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 label
Header - Page headers with breadcrumbs and tabs
Link - Navigation links
List - List component (List, ListRow)
Menu - Dropdown menus (MenuTrigger, Menu, MenuItem, MenuSection, MenuSeparator, SubmenuTrigger)
PasswordField - Password input field
PluginHeader - Plugin-level header with icon, title, tabs, and actions
Popover - Popover overlays
RadioGroup - Radio button groups (RadioGroup, Radio)
SearchAutocomplete - Search input with autocomplete popover (SearchAutocomplete, SearchAutocompleteItem)
SearchField - Search input
Select - Dropdown select (single and multiple selection modes)
Skeleton - Loading skeleton
Slider - Range slider input (label, minValue, maxValue, step)
Switch - Toggle switch
Table - Data tables (with useTable hook for data management)
TablePagination - Standalone pagination component
Tabs - Tab navigation (Tabs, TabList, Tab, TabPanel)
Tag - Tag/chip component (replaces MUI Chip)
TagGroup - Tag/chip groups
Text - Typography component (variant, color, weight, truncate)
TextField - Text input (isRequired, onChange receives string directly)
ToggleButton - Toggle buttons
ToggleButtonGroup - Grouped toggle buttons
Tooltip - Tooltip overlays (TooltipTrigger, Tooltip — both from @backstage/ui)
VisuallyHidden - Accessibility helper
Hooks
useBreakpoint - Responsive breakpoint hook
useTable - Table data management hook (supports complete, offset, and cursor pagination modes)
Migration Patterns
1. Import Changes
Remove MUI 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:
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):
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):
@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);
}
}
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)}
/>
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 CloseRiCloseLineSearchRiSearchLineSettingsRiSettingsLineAddRiAddLineDeleteRiDeleteBinLineEditRiEditLineCheckRiCheckLineErrorRiErrorWarningLineWarningRiAlertLineInfoRiInformationLineExpandMoreRiArrowDownSLineExpandLessRiArrowUpSLineChevronRightRiArrowRightSLineChevronLeftRiArrowLeftSLineMenuRiMenuLineMoreVertRiMore2LineVisibilityRiEyeLineVisibilityOffRiEyeOffLineNewReleasesRiMegaphoneLineRecordVoiceOverRiMegaphoneLineDescriptionRiFileTextLine
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} onChange ={e => 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}
onSelectionChange ={key => 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} onChange ={e => 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} onChange ={setValue} >
<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}
onChange ={(_, newValue ) => setValue(newValue as number)}
min={0}
max={100}
step={10}
/> ;
After (BUI Slider):
import { Slider } from '@backstage/ui' ;
<Slider
label ="Volume"
value ={value}
onChange ={setValue}
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}
onChange ={e => handleChange('a', e.target.checked)}
/>
}
label="Option A"
/>
<FormControlLabel
control ={
<Checkbox
checked ={values.b}
onChange ={e => handleChange('b', e.target.checked)}
/>
}
label="Option B"
/>
</FormGroup >
</FormControl > ;
After (BUI CheckboxGroup):
import { CheckboxGroup , Checkbox } from '@backstage/ui' ;
<CheckboxGroup label ="Options" value ={selected} onChange ={setSelected} >
<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.primaryvar(--bui-fg-primary)text.secondaryvar(--bui-fg-secondary)background.papervar(--bui-bg-neutral-1)background.defaultvar(--bui-bg-app)primary.mainvar(--bui-bg-solid) or var(--bui-ring)error.mainvar(--bui-fg-danger)action.hovervar(--bui-bg-neutral-1-hover)dividervar(--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): The icon param on page extensions expects an IconElement. 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/ui dependency
Add @remixicon/react dependency (if using icons)
Add CSS import to root file
Remove @material-ui/core imports (except components with no BUI equivalent)
Remove @material-ui/icons imports
Remove @material-ui/lab imports (Alert, Pagination now in BUI)
Remove makeStyles and related imports
Create .module.css files for component styles
Replace Typography with Text
Replace Box display="flex" with Flex
Replace Grid container/item with Grid.Root/Grid.Item
Replace Paper with Card
Replace MUI Dialog with BUI DialogTrigger pattern
Replace MUI Tooltip with BUI TooltipTrigger pattern (both from @backstage/ui)
Replace MUI Tabs with BUI Tabs
Replace MUI Menu/Popover with BUI MenuTrigger pattern
Replace Chip with Tag
Replace IconButton with ButtonIcon
Replace MUI Alert with BUI Alert
Replace MUI List with BUI List and ListRow
Replace MUI Select/FormControl with BUI Select
Replace MUI Accordion with BUI Accordion/AccordionTrigger/AccordionPanel
Replace MUI RadioGroup/FormControlLabel with BUI RadioGroup/Radio
Replace MUI FormGroup with BUI CheckboxGroup
Replace MUI Slider with BUI Slider
Update Button props (disabled → isDisabled, variant="contained" → variant="primary")
Update TextField props (required → isRequired, onChange signature)
Replace MUI icons with Remix icons
Run yarn tsc to check for type errors
Run the project's build command (e.g. yarn build, yarn build:all, or yarn workspace <pkg> build) to verify build
Run yarn lint to check for missing dependencies
Test component rendering and functionality
Reference