ワンクリックで
mui-to-bui-migration
// 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.
// 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.
[HINT] SKILL.mdと関連ファイルを含む完全なスキルディレクトリをダウンロード
| 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. |
This skill helps migrate Backstage plugins from Material-UI (@material-ui/core, @material-ui/icons) to Backstage UI ( @backstage/ui).
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';
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)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 (with useTable hook 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, onChange receives string directly)ToggleButton - Toggle buttonsToggleButtonGroup - Grouped toggle buttonsTooltip - Tooltip overlays (TooltipTrigger, Tooltip — both from @backstage/ui)VisuallyHidden - Accessibility helperuseBreakpoint - Responsive breakpoint hookuseTable - Table data management hook (supports complete, offset, and cursor pagination modes)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';
makeStyles to CSS ModulesCreate 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>
)
;
}
FlexBefore (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".
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>
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
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>;
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>;
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"
/ >
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.
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>;
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>;
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.
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>;
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.
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/
| 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) |
| 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) |
| 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) |
| 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) |
Some Backstage APIs still require MUI-compatible icon types:
@backstage/frontend-plugin-api): The icon prop expects MUI IconComponent type. Remix icons
are not type-compatible.@material-ui/lab): No BUI equivalent exists.For these cases, keep using MUI components.
When migrating a plugin:
@backstage/ui dependency@remixicon/react dependency (if using icons)@material-ui/core imports (except components with no BUI equivalent)@material-ui/icons imports@material-ui/lab imports (Alert, Pagination now in BUI)makeStyles and related imports.module.css files for component stylesTypography with TextBox display="flex" with FlexGrid container/item with Grid.Root/Grid.ItemPaper with CardDialog with BUI DialogTrigger patternTooltip with BUI TooltipTrigger pattern (both from @backstage/ui)Tabs with BUI TabsMenu with BUI MenuTrigger patternChip with TagIconButton with ButtonIconAlert with BUI AlertList with BUI List and ListRowButton props (disabled → isDisabled, variant="contained" → variant="primary")TextField props (required → isRequired, onChange signature)yarn tsc to check for type errorsyarn build to verify buildyarn lint to check for missing dependencies