Skip to main content
Version: Next

Customizing Your App's UI

Backstage offers built-in support for both light and dark themes, making it easy to get started with a professional look and feel. But many teams want to go further—tailoring the interface to reflect their organization’s unique brand, identity, and experience.

This section explores the different ways you can customize the appearance of your Backstage instance. You'll learn how the theming system is structured today, how to work with the two coexisting UI systems, and how to define themes that align with your visual language.

Theming architecture overview​

Backstage currently supports two parallel UI systems. The original theming and component model is built on Material UI (MUI), a popular React-based framework. More recently, Backstage introduced Backstage UI (BUI), a custom-designed, CSS-first system developed to meet the platform’s evolving needs. Both systems are supported today, with many parts of the ecosystem still using MUI while new components adopt BUI.

MUI (Legacy)

  • Theming: JS-based with UnifiedThemeProvider
  • Coverage: Most existing plugins
  • Documentation: mui.com

Backstage UI (New)

  • Theming: CSS variables and tokens
  • Coverage: Growing, focused on new work
  • Documentation: ui.backstage.io
info

We recognize that maintaining two separate theming systems is not ideal. Because of the fundamental architectural differences between MUI and Backstage UI, it can be challenging to automate theme updates or know exactly which theme to modify for a given component. Our recommendation is to inspect the component’s code and check its class names: if you see a class name starting with bui, you should use the Backstage UI theming approach to style it.

Creating custom themes​

During the transition to Backstage UI, you will need to maintain themes in two places: some components and plugins still rely on MUI, while others use Backstage UI. To make this easier, the BUI Theme Converter plugin can automatically generate BUI CSS variables from your existing MUI theme, giving you a head start on your Backstage UI theme.

packages/app/src/App.tsx
import { lightTheme, darkTheme } from './themes'; // MUI themes
import './styles.css'; // Backstage UI (BUI) theme

const app = createApp({
apis,
components,
themes: [
{
id: 'light',
title: 'Light theme',
variant: 'light',
icon: <LightIcon />,
Provider: ({ children }) => (
<UnifiedThemeProvider theme={lightTheme} children={children} />
),
},
{
id: 'dark',
title: 'Dark theme',
variant: 'dark',
icon: <DarkIcon />,
Provider: ({ children }) => (
<UnifiedThemeProvider theme={darkTheme} children={children} />
),
},
],
});
NameDescription
idEach theme has a unique id
titleThis will be shown in the settings page to select the right theme.
variantThis can be either light or dark. This is also referred to as mode. On the body of your app we are inserting a data attribute to set the theme based on this value: data-theme-mode="light".
iconThis will be shown in the settings page as a visual element to complement the title.
ProviderThis is needed to set the legacy theme with MUI only. This will be become redundant later on when we fully replace with BUI but for now you need to have it for MUI to work. BUI is based on CSS and don't rely on any global providers.
note

Your list of custom themes overrides the default themes. If you still want to use the default themes, they are exported as themes.light and themes.dark from @backstage/theme. Be sure to provide both light and dark modes so users can choose their preference.

Create a theme for Backstage UI (New)​

Backstage UI is built entirely using CSS. By default we are providing a default theme that include all our core CSS variables and component styles. To start customising Backstage UI to match your brand you need to create a new CSS file and import it directly in packages/app/src/App.tsx. All styles declared in this file will override the default styles. As your file grow you can organise it the way you want or even import multiple files.

Backstage UI is using light by default under :root but you can target it more specifically using the data attribute for mode

packages/app/src/styles.css
:root {
/* Use :root to set styles for both light and dark themes */
.bui-Button {
background-color: #000;
color: #fff;
}
}

[data-theme-mode='light'] {
/* Light theme specific styles */
  --bui-bg-app: #f8f8f8;
--bui-fg-primary: #000;
}

[data-theme-mode='dark'] {
/* Dark theme specific styles */
  --bui-bg-app: #333333;
--bui-fg-primary: #fff;
}

CSS variables​

By adjusting just a few theme variables, you can easily transform the look and feel of your Backstage instance to align with your brand identity. All colors are defined using these variables, ensuring they adapt seamlessly to both light and dark modes.

We recommend starting with a core set of CSS variables to quickly achieve a branded experience. You’ll also find a complete list of available variables below, giving you full flexibility to fine-tune the design to your needs.

And if you’d like to go even further, you can target specific component class names for advanced customization.

Token NameDescription
--bui-bg-appThis is used to define the background color of your app. It will only be used once.
--bui-bg-neutral-1We are using this color to sit on top of --bui-bg-app mostly for Card, Dialog, ...
--bui-bg-neutral-2This is for content inside elevated components. This colour is less common.
--bui-bg-solidThis is used for main actions like primary buttons.
--bui-fg-solidThis is for texts or icons on top of a solid backgrounds.
--bui-fg-primaryYour primary text or icon colours.
--bui-fg-secondaryYour secondary text or icon colours.
--bui-fg-dangerUsed for error states and destructive actions.
--bui-fg-warningUsed for warning states and cautionary information.
--bui-fg-successUsed for success states and positive feedback.
--bui-fg-infoUsed for informational content and neutral status.
--bui-border-1Subtle borders for low-contrast separators.
--bui-border-2Main borders around surfaces like Card, Dialog, ...
--bui-font-regularThe main font of your app.
All available CSS variables

Base colors​

Token NameDescription
--bui-blackPure black color. This one should be the same in light and dark themes.
--bui-whitePure white color. This one should be the same in light and dark themes.

Neutral background colors​

These colors form a layered neutral scale for your application backgrounds. --bui-bg-app is the base background color. Each subsequent level (1 through 4) represents an elevated layer, with hover, pressed, and disabled variants for interactive states.

Token NameDescription
--bui-bg-appThe base background color of your Backstage instance.
--bui-bg-neutral-1First elevated layer. Use for cards, dialogs, and panels.
--bui-bg-neutral-1-hoverHover state for elements on neutral-1.
--bui-bg-neutral-1-pressedPressed state for elements on neutral-1.
--bui-bg-neutral-1-disabledDisabled state for elements on neutral-1.
--bui-bg-neutral-2Second elevated layer. Use for elements on top of neutral-1.
--bui-bg-neutral-2-hoverHover state for elements on neutral-2.
--bui-bg-neutral-2-pressedPressed state for elements on neutral-2.
--bui-bg-neutral-2-disabledDisabled state for elements on neutral-2.
--bui-bg-neutral-3Third elevated layer. Use for elements on top of neutral-2.
--bui-bg-neutral-3-hoverHover state for elements on neutral-3.
--bui-bg-neutral-3-pressedPressed state for elements on neutral-3.
--bui-bg-neutral-3-disabledDisabled state for elements on neutral-3.
--bui-bg-neutral-4Fourth elevated layer. Use for elements on top of neutral-3.
--bui-bg-neutral-4-hoverHover state for elements on neutral-4.
--bui-bg-neutral-4-pressedPressed state for elements on neutral-4.
--bui-bg-neutral-4-disabledDisabled state for elements on neutral-4.

Solid background colors​

Token NameDescription
--bui-bg-solidUsed for solid background colors.
--bui-bg-solid-hoverUsed for solid background colors when hovered.
--bui-bg-solid-pressedUsed for solid background colors when pressed.
--bui-bg-solid-disabledUsed for solid background colors when disabled.

Status background colors​

Token NameDescription
--bui-bg-dangerUsed to show errors information.
--bui-bg-warningUsed to show warnings information.
--bui-bg-successUsed to show success information.
--bui-bg-infoUsed to show informational content.

Foreground colors​

Foreground colours are meant to work in pair with a background colours. Typically this would work for icons, texts, shapes, ... Use a matching name to know what foreground color to use. These colors are prefixed with fg to make it easier to identify.

Token NameDescription
--bui-fg-primaryIt should be used on top of main background surfaces.
--bui-fg-secondaryIt should be used on top of main background surfaces.
--bui-fg-disabledIt should be used on top of main background surfaces.
--bui-fg-solidIt should be used on top of solid background colors.
--bui-fg-dangerUsed for error states and destructive actions.
--bui-fg-warningUsed for warning states and cautionary information.
--bui-fg-successUsed for success states and positive feedback.
--bui-fg-infoUsed for informational content and neutral status.
--bui-fg-danger-on-bgIt should be used on top of danger background colors.
--bui-fg-warning-on-bgIt should be used on top of warning background colors.
--bui-fg-success-on-bgIt should be used on top of success background colors.
--bui-fg-info-on-bgIt should be used on top of info background colors.

Border colors​

These border colors are mostly meant to be used as borders on top of any components with low contrast to help as a separator with the different background colors.

Token NameDescription
--bui-border-1Subtle border for low-contrast separators.
--bui-border-2It should be used on top of --bui-bg-neutral-1.
--bui-border-dangerIt should be used on top of --bui-bg-danger.
--bui-border-warningIt should be used on top of --bui-bg-warning.
--bui-border-successIt should be used on top of --bui-bg-success.
--bui-border-infoIt should be used on top of --bui-bg-info.

Special colors​

These colors are used for special purposes like ring, scrollbar, ...

Token NameDescription
--bui-ringThe color of the ring.
--bui-scrollbarThe color of the scrollbar.
--bui-scrollbar-thumbThe color of the scrollbar thumb.

Font families​

We have two fonts that we use across Backstage UI. The first one is the sans-serif font that we use for the body of the application. The second one is the monospace font that we use for code blocks and tables.

Token NameDescription
--bui-font-regularThe sans-serif font for the theme.
--bui-font-monoThe monospace font for the theme.

Font weights​

We have two font weights that we use across Backstage UI. Regular or Bold.

Token NameDescription
--bui-font-weight-regularThe regular font weight for the theme.
--bui-font-weight-boldThe bold font weight for the theme.

Spacing​

We built a spacing system based on a single value --bui-space. This value is used to calculate the spacing for all the components. By default if you would like to increase or decrease the spacing between your components you can do it simply by updating --bui-space and it will apply to all spacing values.

--bui-space is not used directly in any components but serve as an easy way to calculate the other values.

Token NameDescription
--bui-spaceThe base unit for the spacing system. Default value is 0.25rem.

Radius​

We use a radius system to make sure that the components have a consistent look and feel.

Token NameDescription
--bui-radius-1The radius of the component. Default value is 0.125rem.
--bui-radius-2The radius of the component. Default value is 0.25rem.
--bui-radius-3The radius of the component. Default value is 0.5rem.
--bui-radius-4The radius of the component. Default value is 0.75rem.
--bui-radius-5The radius of the component. Default value is 1rem.
--bui-radius-6The radius of the component. Default value is 1.25rem.
--bui-radius-fullThe radius of the component. Default value is 9999px.

Component class names​

All Backstage UI components come with a set of CSS classes that you can use to style them. To make it easier to identify the class name you can use, we use a specific structure for the class names.

classname-structure

Every component has a unique prefix .bui- followed by the component name. Component props are represented using the data- attribute. That way, class names are easily identifiable.

BUI Theme Converter​

If you already have a custom MUI theme and want to adopt Backstage UI, the BUI Theme Converter plugin can help bridge the gap. It reads the MUI themes installed in your app, maps palette colors, typography, spacing, and border-radius values to the corresponding BUI CSS variables, and lets you preview the result—all without requiring a backend.

The plugin detects every theme registered in your app through the AppThemeApi and, for each one, generates a complete set of BUI CSS custom properties derived from the MUI theme object. The generated output includes colors (background, foreground, border, and status), typography (font family and weights), spacing (when non-default), and border radius (when set to 0).

You can view the generated CSS, copy it to the clipboard, or download it as a .css file. A live preview tab shows common BUI components rendered with your converted theme so you can verify the result before adding it to your app.

To install the plugin, add it to your app:

yarn --cwd packages/app add @backstage/plugin-mui-to-bui

If you are using the new frontend system, the plugin is automatically discovered and no additional wiring is needed. For more details and alternative installation methods, see installing plugins.

If your app uses the old frontend system, add a route manually:

packages/app/src/App.tsx
import { BuiThemerPage } from '@backstage/plugin-mui-to-bui';

// Inside your FlatRoutes:
<Route path="/mui-to-bui" element={<BuiThemerPage />} />;

Once installed, start your Backstage app locally with yarn start and navigate to /mui-to-bui. The page lists every theme installed in your app. For each theme you can switch to the Generated CSS tab to inspect the output, switch to the Live Preview tab to see BUI components rendered with the converted variables, or click Copy CSS / Download CSS to export the result. Paste or import the CSS file into your app (for example as packages/app/src/styles.css) and import it as described in the creating custom themes section above.

Light-theme variables are placed under :root while dark-theme variables use the [data-theme-mode='dark'] selector, matching the convention described in Create a theme for Backstage UI (New).

The converter produces a best-effort mapping. Because MUI and BUI have different design foundations, not every MUI token has a direct BUI equivalent. After generating the CSS you should review the output and adjust values that don't look right—especially neutral background layers and hover/pressed states that MUI doesn't expose directly. Use the live preview as a starting point and refer to the full list of CSS variables for further tweaking.

Create a theme for MUI (Legacy)​

To customize the appearance of your Backstage app using the legacy MUI theming system, you can define your own theme by extending the built-in light or dark themes. This is done using the createUnifiedTheme utility provided by the @backstage/theme package. This function allows you to override key aspects of the theme—such as color palette, typography, spacing, and shape—while preserving Backstage’s base configuration and component compatibility.

The example below shows how to create a new theme based on the default light theme:

packages/app/src/themes.ts
import {
createBaseThemeOptions,
createUnifiedTheme,
palettes,
} from '@backstage/theme';

export const lightTheme = createUnifiedTheme({
...createBaseThemeOptions({
palette: palettes.light,
}),
fontFamily: 'Comic Sans MS',
defaultPageTheme: 'home',
});

export const darkTheme = createUnifiedTheme({
...createBaseThemeOptions({
palette: palettes.dark,
}),
fontFamily: 'Comic Sans MS',
defaultPageTheme: 'home',
});

You can also create a theme from scratch that matches the BackstageTheme type exported by @backstage/theme. See the Material UI docs on theming for more information about how that can be done.

Example of a custom MUI theme

For a more complete example of a custom theme including Backstage and Material UI component overrides, see the Aperture theme from the Backstage demo site.

packages/app/src/themes.ts
import {
createBaseThemeOptions,
createUnifiedTheme,
genPageTheme,
palettes,
shapes,
} from '@backstage/theme';

export const myTheme = createUnifiedTheme({
...createBaseThemeOptions({
palette: {
...palettes.light,
primary: {
main: '#343b58',
},
secondary: {
main: '#565a6e',
},
error: {
main: '#8c4351',
},
warning: {
main: '#8f5e15',
},
info: {
main: '#34548a',
},
success: {
main: '#485e30',
},
background: {
default: '#d5d6db',
paper: '#d5d6db',
},
banner: {
info: '#34548a',
error: '#8c4351',
text: '#343b58',
link: '#565a6e',
},
errorBackground: '#8c4351',
warningBackground: '#8f5e15',
infoBackground: '#343b58',
navigation: {
background: '#343b58',
indicator: '#8f5e15',
color: '#d5d6db',
selectedColor: '#ffffff',
},
},
}),
defaultPageTheme: 'home',
fontFamily: 'Comic Sans MS',
/* below drives the header colors */
pageTheme: {
home: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }),
documentation: genPageTheme({
colors: ['#8c4351', '#343b58'],
shape: shapes.wave2,
}),
tool: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.round }),
service: genPageTheme({
colors: ['#8c4351', '#343b58'],
shape: shapes.wave,
}),
website: genPageTheme({
colors: ['#8c4351', '#343b58'],
shape: shapes.wave,
}),
library: genPageTheme({
colors: ['#8c4351', '#343b58'],
shape: shapes.wave,
}),
other: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }),
app: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }),
apis: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }),
},
});
Custom Typography

When creating a custom theme you can also customize various aspects of the default typography, here's an example using simplified theme:

packages/app/src/theme/myTheme.ts
import {
createBaseThemeOptions,
createUnifiedTheme,
palettes,
} from '@backstage/theme';

export const myTheme = createUnifiedTheme({
...createBaseThemeOptions({
palette: palettes.light,
typography: {
htmlFontSize: 16,
fontFamily: 'Arial, sans-serif',
h1: {
fontSize: 54,
fontWeight: 700,
marginBottom: 10,
},
h2: {
fontSize: 40,
fontWeight: 700,
marginBottom: 8,
},
h3: {
fontSize: 32,
fontWeight: 700,
marginBottom: 6,
},
h4: {
fontWeight: 700,
fontSize: 28,
marginBottom: 6,
},
h5: {
fontWeight: 700,
fontSize: 24,
marginBottom: 4,
},
h6: {
fontWeight: 700,
fontSize: 20,
marginBottom: 2,
},
},
defaultPageTheme: 'home',
}),
});

If you wanted to only override a sub-set of the typography setting, for example just h1 then you would do this:

packages/app/src/theme/myTheme.ts
import {
createBaseThemeOptions,
createUnifiedTheme,
defaultTypography,
palettes,
} from '@backstage/theme';

export const myTheme = createUnifiedTheme({
...createBaseThemeOptions({
palette: palettes.light,
typography: {
...defaultTypography,
htmlFontSize: 16,
fontFamily: 'Roboto, sans-serif',
h1: {
fontSize: 72,
fontWeight: 700,
marginBottom: 10,
},
},
defaultPageTheme: 'home',
}),
});
Custom Fonts

To add custom fonts, you first need to store the font so that it can be imported. We suggest creating the assets/fonts directory in your front-end application src folder.

You can then declare the font style following the @font-face syntax from Material UI Typography.

After that you can then utilize the styleOverrides of MuiCssBaseline under components to add a font to the @font-face array.

packages/app/src/theme/myTheme.ts
import MyCustomFont from '../assets/fonts/My-Custom-Font.woff2';

const myCustomFont = {
fontFamily: 'My-Custom-Font',
fontStyle: 'normal',
fontDisplay: 'swap',
fontWeight: 300,
src: `
local('My-Custom-Font'),
url(${MyCustomFont}) format('woff2'),
`,
};

export const myTheme = createUnifiedTheme({
fontFamily: 'My-Custom-Font',
palette: palettes.light,
components: {
MuiCssBaseline: {
styleOverrides: {
'@font-face': [myCustomFont],
},
},
},
});

If you want to utilize different or multiple fonts, then you can set the top level fontFamily to what you want for your body, and then override fontFamily in typography to control fonts for various headings.

packages/app/src/theme/myTheme.ts
import MyCustomFont from '../assets/fonts/My-Custom-Font.woff2';
import myAwesomeFont from '../assets/fonts/My-Awesome-Font.woff2';

const myCustomFont = {
fontFamily: 'My-Custom-Font',
fontStyle: 'normal',
fontDisplay: 'swap',
fontWeight: 300,
src: `
local('My-Custom-Font'),
url(${MyCustomFont}) format('woff2'),
`,
};

const myAwesomeFont = {
fontFamily: 'My-Awesome-Font',
fontStyle: 'normal',
fontDisplay: 'swap',
fontWeight: 300,
src: `
local('My-Awesome-Font'),
url(${myAwesomeFont}) format('woff2'),
`,
};

export const myTheme = createUnifiedTheme({
fontFamily: 'My-Custom-Font',
components: {
MuiCssBaseline: {
styleOverrides: {
'@font-face': [myCustomFont, myAwesomeFont],
},
},
},
...createBaseThemeOptions({
palette: palettes.light,
typography: {
...defaultTypography,
htmlFontSize: 16,
fontFamily: 'My-Custom-Font',
h1: {
fontSize: 72,
fontWeight: 700,
marginBottom: 10,
fontFamily: 'My-Awesome-Font',
},
},
defaultPageTheme: 'home',
}),
});
Overriding Backstage and Material UI components styles

When creating a custom theme you would be applying different values to component's CSS rules that use the theme object. For example, a Backstage component's styles might look like this:

const useStyles = makeStyles<BackstageTheme>(
theme => ({
header: {
padding: theme.spacing(3),
boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)',
backgroundImage: theme.page.backgroundImage,
},
}),
{ name: 'BackstageHeader' },
);

Notice how the padding is getting its value from theme.spacing, that means that setting a value for spacing in your custom theme would affect this component padding property and the same goes for backgroundImage which uses theme.page.backgroundImage. However, the boxShadow property doesn't reference any value from the theme, that means that creating a custom theme wouldn't be enough to alter the box-shadow property or to add css rules that aren't already defined like a margin. For these cases you should also create an override.

Here's how you would do that:

packages/app/src/theme/myTheme.ts
import {
createBaseThemeOptions,
createUnifiedTheme,
palettes,
} from '@backstage/theme';

export const myTheme = createUnifiedTheme({
...createBaseThemeOptions({
palette: palettes.light,
}),
fontFamily: 'Comic Sans MS',
defaultPageTheme: 'home',
components: {
BackstageHeader: {
styleOverrides: {
header: ({ theme }) => ({
width: 'auto',
margin: '20px',
boxShadow: 'none',
borderBottom: `4px solid ${theme.palette.primary.main}`,
}),
},
},
},
});
Missing v5 prefix for MUI 5 class names

If you are using MUI 5 components in the main app, you may notice that the rendered elements have a v5- prefix in front of the MUI class names, but not when you try to use the class name props in code.

Example:

<button class="v5-MuiButtonBase-root v5-MuiButton-root ..." ...
import { buttonClasses } from '@mui/material/Button'

...

console.log(buttonClasses.root)
// outputs "MuiButton-root" instead of "v5-MuiButton-root"

The reason for this is that the UnifiedThemeProvider is configuring the MUI class name generator function too late. According to MUI 5 docs, it should be configured before any MUI 5 components load.

To resolve this issue:

  1. Create a file that configures the class name generator, e.g. packages/app/src/MuiClassnameSetup.ts, containing:
// this replicates functionality from UnifiedThemeProvider
import { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material/className';

ClassNameGenerator.configure(componentName => {
if ((componentName ?? '').startsWith('v5-')) {
return componentName;
}
return `v5-${componentName}`;
});
  1. Import this as the very first thing in packages/app/src/index.tsx
// CRITICAL: Must be first import so that static MUI V5 class names are
// generated before any MUI V5 components load
import './MuiClassnameSetup'

...