docs(bep): define shared AI assets repo and cli support

this PR introduces BEP for backstage managed AI assets in the backstage
monorepo that can be shared with backstage contributors and adaptors.

added first skill to the docs to test this out. locally it's possible to
run: npx skills add ./docs which adds the mui-to-bui skill to be used.

Signed-off-by: Hellgren Heikki <heikki.hellgren@op.fi>
This commit is contained in:
Hellgren Heikki
2026-03-06 10:30:27 +02:00
parent 5361cbdcfc
commit 907682f945
4 changed files with 1063 additions and 1 deletions
+9
View File
@@ -0,0 +1,9 @@
{
"skills": [
{
"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.",
"files": ["SKILL.md"]
}
]
}
@@ -0,0 +1,781 @@
---
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:
1. Install the BUI package:
```bash
yarn add @backstage/ui
```
2. Add the CSS import to your root file (typically `src/index.ts` or app entry point):
```typescript
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
- `Grid` - CSS Grid-based layout (`Grid.Root`, `Grid.Item`)
- `Flex` - `Flexbox` layout component
### UI Components
- `Accordion` - Collapsible content panels
- `Avatar` - User/entity avatars
- `Button` - Primary action buttons (`variant="primary"`, `variant="secondary"`, `isDisabled`)
- `ButtonIcon` - Icon-only buttons (`icon`, `onPress`, `variant`)
- `ButtonLink` - Link styled as button
- `Card` - Content cards (`Card`, `CardHeader`, `CardBody`, `CardFooter`)
- `Checkbox` - Checkbox input
- `Dialog` - Modal dialogs (`DialogTrigger`, `Dialog`, `DialogHeader`, `DialogBody`, `DialogFooter`)
- `Header` - Page headers
- `HeaderPage` - Full page header component
- `Link` - Navigation links
- `Menu` - Dropdown menus (`MenuTrigger`, `Menu`, `MenuItem`)
- `PasswordField` - Password input field
- `Popover` - Popover overlays
- `RadioGroup` - Radio button groups
- `SearchField` - Search input
- `Select` - Dropdown select
- `Skeleton` - Loading skeleton
- `Switch` - Toggle switch
- `Table` - Data tables
- `Tabs` - Tab navigation (`Tabs`, `TabList`, `Tab`, `TabPanel`)
- `Tag` - Tag/chip component (replaces MUI Chip)
- `TagGroup` - Tag/chip groups
- `Text` - Typography component (`variant`, `color`)
- `TextField` - Text input (`isRequired`, `onChange` receives string directly)
- `ToggleButton` - Toggle buttons
- `ToggleButtonGroup` - Grouped toggle buttons
- `Tooltip` - 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:**
```typescript
// 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:**
```typescript
// 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`):**
```typescript
// 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):**
```css
/* 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);
}
}
```
```typescript
// 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):**
```typescript
<Box
display = "flex"
flexDirection = "column"
alignItems = "center"
justifyContent = "space-between"
>
<Box display = "flex"
flexDirection = "row"
gap = {2} >
{children}
< /Box>
< /Box>
```
**After (BUI `Flex` component):**
```typescript
<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):**
```typescript
<Grid container
spacing = {3} >
<Grid item
xs = {12}
md = {6} >
{content}
< /Grid>
< /Grid>
```
**After (BUI Grid):**
```typescript
<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):**
```typescript
<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):**
```typescript
<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):**
```typescript
import {Tooltip, Typography} from '@material-ui/core';
<Tooltip title = { < Typography > Tooltip
content < /Typography>}>
< span > Hover
me < /span>
< /Tooltip>;
```
**After (BUI TooltipTrigger pattern):**
```typescript
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):**
```typescript
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):**
```typescript
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):**
```typescript
<Button variant = "contained"
color = "primary"
disabled = {loading}
onClick = {handleClick} >
Submit
< /Button>
< IconButton
onClick = {handleDelete}
disabled = {!
canDelete
}>
<DeleteIcon / >
</IconButton>
```
**After (BUI Button):**
```typescript
<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):**
```typescript
<TextField
required
name="title"
label="Title"
value={value}
onChange={e => setValue(e.target.value)}
fullWidth
/>
```
**After (BUI TextField):**
```typescript
<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):**
```typescript
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):**
```typescript
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):**
```typescript
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):**
```typescript
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):**
```typescript
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):**
```css
/* 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);
}
}
```
```typescript
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):**
```typescript
import { Chip } from '@material-ui/core';
<Chip label="Category" size="small" />;
```
**After (BUI Tag):**
```typescript
import {Tag} from '@backstage/ui';
<Tag size = "small" > Category < /Tag>;
```
### 14. Icons: MUI Icons to Remix Icons
**Before (MUI Icons):**
```typescript
import CloseIcon from '@material-ui/icons/Close';
import SearchIcon from '@material-ui/icons/Search';
<CloseIcon / >
<SearchIcon fontSize = "small" / >
```
**After (Remix Icons):**
```typescript
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`): The `icon` prop expects MUI `IconComponent` type. 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:
1. [ ] Add `@backstage/ui` dependency
2. [ ] Add `@remixicon/react` dependency (if using icons)
3. [ ] Add `react-aria-components` dependency (if using Tooltip)
4. [ ] Add CSS import to root file
5. [ ] Remove `@material-ui/core` imports (except components with no BUI equivalent)
6. [ ] Remove `@material-ui/icons` imports
7. [ ] Remove `makeStyles` and related imports
8. [ ] Create `.module.css` files for component styles
9. [ ] Replace `Typography` with `Text`
10. [ ] Replace `Box display="flex"` with `Flex`
11. [ ] Replace `Grid container/item` with `Grid.Root/Grid.Item`
12. [ ] Replace `Paper` with `Card`
13. [ ] Replace MUI `Dialog` with BUI `DialogTrigger` pattern
14. [ ] Replace MUI `Tooltip` with BUI `TooltipTrigger` pattern
15. [ ] Replace MUI `Tabs` with BUI `Tabs`
16. [ ] Replace MUI `Menu` with BUI `MenuTrigger` pattern
17. [ ] Replace `Chip` with `Tag`
18. [ ] Replace `IconButton` with `ButtonIcon`
19. [ ] Update `Button` props (`disabled` → `isDisabled`, `variant="contained"` → `variant="primary"`)
20. [ ] Update `TextField` props (`required` → `isRequired`, `onChange` signature)
21. [ ] Replace MUI icons with Remix icons
22. [ ] Run `yarn tsc` to check for type errors
23. [ ] Run `yarn build` to verify build
24. [ ] Run `yarn lint` to check for missing dependencies
25. [ ] 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