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
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.
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} />
),
},
],
});
Name | Description |
---|---|
id | Each theme has a unique id |
title | This will be shown in the settings page to select the right theme. |
variant | This 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" . |
icon | This will be shown in the settings page as a visual element to complement the title. |
Provider | This 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. |
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
: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 Name | Description |
---|---|
--bui-bg | This is used to define the background color of your app. It will only be used once. |
--bui-bg-surface-1 | We ar using this color to sit on top of --bui-bg mostly for Card , Dialog , ... |
--bui-bg-surface-2 | This is for content inside elevated components. This colour is less common. |
--bui-bg-solid | This is used for main actions like primary buttons. |
--bui-fg-solid | This is for texts or icons on top of a solid backgrounds. |
--bui-fg-primary | Your primary text or icon colours. |
--bui-fg-secondary | Your secondary text or icon colours. |
--bui-fg-link | Used for links. |
--bui-border | Main borders around surfaces like Card , Dialog , ... |
--bui-font-regular | The main font of your app. |
All available CSS variables
Base colors
These colors are used for special purposes like ring, scrollbar, ...
Token Name | Description |
---|---|
--bui-black | Pure black color. This one should be the same in light and dark themes. |
--bui-white | Pure white color. This one should be the same in light and dark themes. |
--bui-gray-1 | You can use these mostly for backgrounds colors. |
--bui-gray-2 | You can use these mostly for backgrounds colors. |
--bui-gray-3 | You can use these mostly for backgrounds colors. |
--bui-gray-4 | You can use these mostly for backgrounds colors. |
--bui-gray-5 | You can use these mostly for backgrounds colors. |
--bui-gray-6 | You can use these mostly for backgrounds colors. |
--bui-gray-7 | You can use these mostly for backgrounds colors. |
--bui-gray-8 | You 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 Name | Description |
---|---|
--bui-bg | The background color of your Backstage instance. |
--bui-bg-surface-1 | Use for any panels or elevated surfaces. |
--bui-bg-surface-2 | Use for any panels or elevated surfaces. |
--bui-bg-solid | Used for solid background colors. |
--bui-bg-solid-hover | Used for solid background colors when hovered. |
--bui-bg-solid-pressed | Used for solid background colors when pressed. |
--bui-bg-solid-disabled | Used for solid background colors when disabled. |
--bui-bg-tint | Used for tint background colors. |
--bui-bg-tint-hover | Used for tint background colors when hovered. |
--bui-bg-tint-focus | Used for tint background colors when active. |
--bui-bg-tint-disabled | Used for tint background colors when disabled. |
--bui-bg-danger | Used to show errors information. |
--bui-bg-warning | Used to show warnings information. |
--bui-bg-success | Used 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 Name | Description |
---|---|
--bui-fg-primary | It should be used on top of main background surfaces. |
--bui-fg-secondary | It should be used on top of main background surfaces. |
--bui-fg-link | It should be used on top of main background surfaces. |
--bui-fg-link-hover | It should be used on top of main background surfaces. |
--bui-fg-disabled | It should be used on top of main background surfaces. |
--bui-fg-solid | It should be used on top of solid background colors. |
--bui-fg-tint | It should be used on top of tint background colors. |
--bui-fg-tint-disabled | It should be used on top of tint background colors when disabled. |
--bui-fg-danger | It should be used on top of danger background colors. |
--bui-fg-warning | It should be used on top of warning background colors. |
--bui-fg-success | It 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 Name | Description |
---|---|
--bui-border | It should be used on top of --bui-bg-surface-1 . |
--bui-border-hover | Used when the component is interactive and hovered. |
--bui-border-pressed | Used when the component is interactive and hovered. |
--bui-border-disabled | Used when the component is disabled. |
--bui-border-danger | It should be used on top of --bui-bg-danger . |
--bui-border-warning | It should be used on top of --bui-bg-warning . |
--bui-border-success | It should be used on top of --bui-bg-success . |
Special colors
These colors are used for special purposes like ring, scrollbar, ...
Token Name | Description |
---|---|
--bui-ring | The color of the ring. |
--bui-scrollbar | The color of the scrollbar. |
--bui-scrollbar-thumb | The 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 Name | Description |
---|---|
--bui-font-regular | The sans-serif font for the theme. |
--bui-font-mono | The monospace font for the theme. |
Font weights
We have two font weights that we use across Backstage UI. Regular or Bold.
Token Name | Description |
---|---|
--bui-font-weight-regular | The regular font weight for the theme. |
--bui-font-weight-bold | The 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 Name | Description |
---|---|
--bui-space | The 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 Name | Description |
---|---|
--bui-radius-1 | The radius of the component. Default value is 0.125rem . |
--bui-radius-2 | The radius of the component. Default value is 0.25rem . |
--bui-radius-3 | The radius of the component. Default value is 0.5rem . |
--bui-radius-4 | The radius of the component. Default value is 0.75rem . |
--bui-radius-5 | The radius of the component. Default value is 1rem . |
--bui-radius-6 | The radius of the component. Default value is 1.25rem . |
--bui-radius-full | The 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.
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:
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.
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:
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:
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.
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.
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:
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}`,
}),
},
},
},
});