Skip to main content

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. We are working on a plugin that will make help you convert your existing MUI theme into a Backstage UI CSS file you can add to your application. We'll update this page when the plugin is available but for now you can follow progress on this PR #31140.

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: #f8f8f8;
--bui-fg-primary: #000;
}

[data-theme-mode='dark'] {
/* Dark theme specific styles */
  --bui-bg: #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-bgThis is used to define the background color of your app. It will only be used once.
--bui-bg-surface-1We ar using this color to sit on top of --bui-bg mostly for Card, Dialog, ...
--bui-bg-surface-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-linkUsed for links.
--bui-borderMain borders around surfaces like Card, Dialog, ...
--bui-font-regularThe main font of your app.
All available CSS variables

Base colors

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

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.
--bui-gray-1You can use these mostly for backgrounds colors.
--bui-gray-2You can use these mostly for backgrounds colors.
--bui-gray-3You can use these mostly for backgrounds colors.
--bui-gray-4You can use these mostly for backgrounds colors.
--bui-gray-5You can use these mostly for backgrounds colors.
--bui-gray-6You can use these mostly for backgrounds colors.
--bui-gray-7You can use these mostly for backgrounds colors.
--bui-gray-8You can use these mostly for backgrounds colors.

Core background colors

These colors are used for the background of your application. We are mostly using for now a single elevated background for panels. --bui-bg should mostly use as the main background color of your app.

Token NameDescription
--bui-bgThe background color of your Backstage instance.
--bui-bg-surface-1Use for any panels or elevated surfaces.
--bui-bg-surface-2Use for any panels or elevated surfaces.
--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.
--bui-bg-tintUsed for tint background colors.
--bui-bg-tint-hoverUsed for tint background colors when hovered.
--bui-bg-tint-focusUsed for tint background colors when active.
--bui-bg-tint-disabledUsed for tint background colors when disabled.
--bui-bg-dangerUsed to show errors information.
--bui-bg-warningUsed to show warnings information.
--bui-bg-successUsed to show success information.

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-linkIt should be used on top of main background surfaces.
--bui-fg-link-hoverIt 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-tintIt should be used on top of tint background colors.
--bui-fg-tint-disabledIt should be used on top of tint background colors when disabled.
--bui-fg-dangerIt should be used on top of danger background colors.
--bui-fg-warningIt should be used on top of warning background colors.
--bui-fg-successIt should be used on top of success 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-borderIt should be used on top of --bui-bg-surface-1.
--bui-border-hoverUsed when the component is interactive and hovered.
--bui-border-pressedUsed when the component is interactive and hovered.
--bui-border-disabledUsed when the component is disabled.
--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.

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.

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}`,
}),
},
},
},
});