When to use this skill
- Authoring or editing any frontend component in a Backstage plugin
- Picking the right component for layout, typography, form input, dialog, menu, tab, table, etc.
- Styling components (BUI uses CSS modules + design tokens, not
makeStyles) - Adding icons (use
@remixicon/react)
Setup
A new plugin scaffolded with yarn new includes @backstage/ui automatically. If installing manually:
yarn add @backstage/ui @remixicon/react
Then add the stylesheet to your app entry (packages/app/src/index.tsx or similar):
import '@backstage/ui/css/styles.css';
Choose the right component
| Need | Component | Reference |
|---|---|---|
| Page layout / wrappers | Box, Container, Flex, FullPage, Grid |
references/components.md |
| Typography | Text |
references/components.md |
| Buttons / actions | Button, ButtonIcon, ButtonLink |
references/components.md |
| Forms | TextField, PasswordField, SearchField, Select, Combobox, Checkbox, RadioGroup, Switch, DatePicker, DateRangePicker |
references/components.md |
| Feedback | Alert, Skeleton, Tooltip, Tag, TagGroup |
references/components.md |
| Overlays | Dialog, Menu, Popover |
references/components.md |
| Navigation | Tabs, Header, PluginHeader, Link |
references/components.md |
| Data | Table (+ useTable hook), List (+ ListRow), Avatar, Card |
references/components.md |
| Icons | @remixicon/react (e.g. RiSearchLine, RiAddLine) |
references/icons.md |
| Styling | CSS modules with BUI tokens (--bui-space-*, --bui-fg-*, --bui-bg-*) |
references/styling.md |
Two invocation modes
- Standalone (mid-development): user is editing an existing plugin component. Pick the relevant reference and apply.
- Post-scaffold (called by
creating-backstage-pluginfor any frontend template): apply BUI defaults to the freshly scaffolded plugin's example component, ensuring imports come from@backstage/uiand styles use CSS modules with BUI tokens.
Common gotchas
- BUI ≠ Material UI. Don't mix prop names:
disabled→isDisabled;variant="contained"→variant="primary". Flexusesjustify="between"(NOTspace-between);gapgoes via inlinestyleor BUI tokens.TooltiprequiresTooltipTriggeras a parent.TextField'sonChangereceives a string directly, not an event object.- One known fallback where BUI doesn't have an equivalent:
@material-ui/labTimelinehas no BUI replacement. Use MUI in that specific case.
Data fetching convention
Don't manually wire useState for loading/error/data. Use react-use hooks:
useAsync(fn, deps)— runs the async function on mount and whenever deps change. Use for "load data when the component renders or its props change."useAsyncFn(fn)— returns[state, callback]. Use for "load data when the user does X" (button click, form submit, etc.).
Manual useState for { loading, error, value } is an anti-pattern in this codebase: it duplicates logic the hook handles correctly (race conditions, unmount-during-fetch, etc.). The catalog and identity references show useAsync; the same applies anywhere data is fetched.
import useAsyncFn from 'react-use/lib/useAsyncFn';
import { Button, Skeleton, Alert, Text } from '@backstage/ui';
export const FetchOnDemand = () => {
const [{ loading, error, value }, fetch] = useAsyncFn(async (id: string) => {
return await someApi.get(id);
}, []);
return (
<>
<Button onClick={() => fetch('foo')}>Fetch</Button>
{loading && <Skeleton width={200} height={20} />}
{error && <Alert status="danger" title={error.message} />}
{value && <Text>{value.name}</Text>}
</>
);
};