From 4d2816dbe908db34a8ecd13e4cb899d6d1b196b5 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 10 Sep 2025 13:54:15 +0100 Subject: [PATCH 01/11] First pass improving theming docs Signed-off-by: Charles de Dreuille --- docs/conf/user-interface/icons.md | 124 +++++ docs/conf/user-interface/index.md | 471 +++++++++++++++++ docs/conf/user-interface/logo.md | 25 + docs/conf/user-interface/sidebar.md | 93 ++++ docs/dls/component-design-guidelines.md | 6 +- docs/dls/design.md | 2 +- docs/features/search/how-to-guides.md | 2 +- docs/getting-started/app-custom-theme.md | 613 ----------------------- microsite/sidebars.ts | 11 +- 9 files changed, 729 insertions(+), 618 deletions(-) create mode 100644 docs/conf/user-interface/icons.md create mode 100644 docs/conf/user-interface/index.md create mode 100644 docs/conf/user-interface/logo.md create mode 100644 docs/conf/user-interface/sidebar.md delete mode 100644 docs/getting-started/app-custom-theme.md diff --git a/docs/conf/user-interface/icons.md b/docs/conf/user-interface/icons.md new file mode 100644 index 0000000000..44b8782430 --- /dev/null +++ b/docs/conf/user-interface/icons.md @@ -0,0 +1,124 @@ +--- +id: icons +title: Customizing Icons +sidebar_label: Icons +description: Customizing Icons +--- + +So far you've seen how to create your own theme and add your own logo, in the following sections you'll be shown how to override the existing icons and how to add more icons + +## Custom Icons + +You can also customize the Project's _default_ icons. + +You can change the following [icons](https://github.com/backstage/backstage/blob/master/packages/app-defaults/src/defaults/icons.tsx). + +### Requirements + +- Files in `.svg` format +- React components created for the icons + +### Create React Component + +In your front-end application, locate the `src` folder. We suggest creating the `assets/icons` directory and `CustomIcons.tsx` file. + +```tsx title="customIcons.tsx" +import { SvgIcon, SvgIconProps } from '@material-ui/core'; + +export const ExampleIcon = (props: SvgIconProps) => ( + + + +); +``` + +### Using the custom icon + +Supply your custom icon in `packages/app/src/App.tsx` + +```tsx title="packages/app/src/App.tsx" +/* highlight-add-next-line */ +import { ExampleIcon } from './assets/icons/CustomIcons' + + +const app = createApp({ + apis, + components: { + {/* ... */} + }, + themes: [ + {/* ... */} + ], + /* highlight-add-start */ + icons: { + github: ExampleIcon, + }, + /* highlight-add-end */ + bindRoutes({ bind }) { + {/* ... */} + } +}) +``` + +## Adding Icons + +You can add more icons, if the [default icons](https://github.com/backstage/backstage/blob/master/packages/app-defaults/src/defaults/icons.tsx) do not fit your needs, so that they can be used in other places like for Links in your entities. For this example we'll be using icons from[Material UI](https://v4.mui.com/components/material-icons/) and specifically the `AlarmIcon`. Here's how to do that: + +1. First you will want to open your `App.tsx` in `/packages/app/src` +2. Then you want to import your icon, add this to the rest of your imports: `import AlarmIcon from '@material-ui/icons/Alarm';` +3. Next you want to add the icon like this to your `createApp`: + + ```tsx title="packages/app/src/App.tsx" + const app = createApp({ + apis: ..., + plugins: ..., + /* highlight-add-start */ + icons: { + alert: AlarmIcon, + }, + /* highlight-add-end */ + themes: ..., + components: ..., + }); + ``` + +4. Now we can reference `alert` for our icon in our entity links like this: + + ```yaml + apiVersion: backstage.io/v1alpha1 + kind: Component + metadata: + name: artist-lookup + description: Artist Lookup + links: + - url: https://example.com/alert + title: Alerts + icon: alert + ``` + + And this is the result: + + ![Example Link with Alert icon](../../assets/getting-started/add-icons-links-example.png) + + Another way you can use these icons is from the `AppContext` like this: + + ```ts + import { useApp } from '@backstage/core-plugin-api'; + + const app = useApp(); + const alertIcon = app.getSystemIcon('alert'); + ``` + + You might want to use this method if you have an icon you want to use in several locations. + +:::note Note + +If the icon is not available as one of the default icons or one you've added then it will fall back to Material UI's `LanguageIcon` + +::: diff --git a/docs/conf/user-interface/index.md b/docs/conf/user-interface/index.md new file mode 100644 index 0000000000..9be726d2dc --- /dev/null +++ b/docs/conf/user-interface/index.md @@ -0,0 +1,471 @@ +--- +id: index +title: Customizing Your App's UI +sidebar_label: Introduction +description: Learn how to customize the look and feel of your Backstage app, including theming and branding options. +--- + +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. At this time, there is no automated solution to generate MUI themes from your Backstage UI theme, so you will need to manage both separately until the migration is complete. + +```tsx title="packages/app/src/App.tsx" +/* highlight-add-start */ +import { lightTheme, darkTheme } from './themes'; // MUI themes +import './styles.css'; // Backstage UI (BUI) theme +/* highlight-add-end */ + +const app = createApp({ + apis, + components, + /* highlight-add-start */ + themes: [ + { + id: 'light', + title: 'Light theme', + variant: 'light', + icon: , + Provider: ({ children }) => ( + + ), + }, + { + id: 'dark', + title: 'Dark theme', + variant: 'dark', + icon: , + Provider: ({ children }) => ( + + ), + }, + ], + /* highlight-add-end */ +}); +``` + +| 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 refered 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. | + +:::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`](https://www.npmjs.com/package/@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 + +```css title="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; +} +``` + +### Available tokens + +### Component class names + +### Custom font + +## 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`](https://www.npmjs.com/package/@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: + +```ts title="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`](https://www.npmjs.com/package/@backstage/theme). See the +[Material UI docs on theming](https://material-ui.com/customization/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](https://github.com/backstage/demo/blob/master/packages/app/src/theme/aperture.ts) from the [Backstage demo site](https://demo.backstage.io). + +```ts title="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: + +```ts title="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: + +```ts title="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](https://mui.com/material-ui/customization/typography/). + +After that you can then utilize the `styleOverrides` of `MuiCssBaseline` under components to add a font to the `@font-face` array. + +```ts title="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. + +```ts title="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: + +```tsx +const useStyles = makeStyles( + 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: + +```ts title="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}`, + }), + }, + }, + }, +}); +``` + +
diff --git a/docs/conf/user-interface/logo.md b/docs/conf/user-interface/logo.md new file mode 100644 index 0000000000..35789d6330 --- /dev/null +++ b/docs/conf/user-interface/logo.md @@ -0,0 +1,25 @@ +--- +id: logo +title: Customizing Your Logo +sidebar_label: Logo +description: Learn how to customize your logo. +--- + +In addition to a custom theme, you can also customize the logo displayed at the far top left of the site. + +In your frontend app, locate `src/components/Root/` folder. You'll find two components: + +- `LogoFull.tsx` - A larger logo used when the Sidebar navigation is opened. +- `LogoIcon.tsx` - A smaller logo used when the Sidebar navigation is closed. + +To replace the images, you can simply replace the relevant code in those components with raw SVG definitions. + +You can also use another web image format such as PNG by importing it. To do this, place your new image into a new subdirectory such as `src/components/Root/logo/my-company-logo.png`, and then add this code: + +```tsx +import MyCustomLogoFull from './logo/my-company-logo.png'; + +const LogoFull = () => { + return ; +}; +``` diff --git a/docs/conf/user-interface/sidebar.md b/docs/conf/user-interface/sidebar.md new file mode 100644 index 0000000000..f66e6bd106 --- /dev/null +++ b/docs/conf/user-interface/sidebar.md @@ -0,0 +1,93 @@ +--- +id: sidebar +title: Customizing Your Sidebar +sidebar_label: Sidebar +description: Learn how to customize the look and feel of your Sidebar. +--- + +As you've seen there are many ways that you can customize your Backstage app. The following section will show you how you can customize the sidebar. + +## Sidebar Sub-menu + +For this example we'll show you how you can expand the sidebar with a sub-menu: + +1. Open the `Root.tsx` file located in `packages/app/src/components/Root` as this is where the sidebar code lives +2. Then we want to add the following import for `useApp`: + + ```tsx title="packages/app/src/components/Root/Root.tsx" + import { useApp } from '@backstage/core-plugin-api'; + ``` + +3. Then update the `@backstage/core-components` import like this: + + ```tsx title="packages/app/src/components/Root/Root.tsx" + import { + Sidebar, + sidebarConfig, + SidebarDivider, + SidebarGroup, + SidebarItem, + SidebarPage, + SidebarScrollWrapper, + SidebarSpace, + useSidebarOpenState, + Link, + /* highlight-add-start */ + GroupIcon, + SidebarSubmenu, + SidebarSubmenuItem, + /* highlight-add-end */ + } from '@backstage/core-components'; + ``` + +4. Finally replace `` with this: + + ```tsx title="packages/app/src/components/Root/Root.tsx" + + + + + + + + + + + + + + ``` + +When you startup your Backstage app and hover over the Home option on the sidebar you'll now see a nice sub-menu appear with links to the various Kinds in your Catalog. It would look like this: + +![Sidebar sub-menu example](./../../assets/getting-started/sidebar-submenu-example.png) + +You can see more ways to use this in the [Storybook Sidebar examples](https://backstage.io/storybook/?path=/story/layout-sidebar--sample-scalable-sidebar) diff --git a/docs/dls/component-design-guidelines.md b/docs/dls/component-design-guidelines.md index 35e2f987d2..17e9d1f11a 100644 --- a/docs/dls/component-design-guidelines.md +++ b/docs/dls/component-design-guidelines.md @@ -103,6 +103,8 @@ accessibility. [7]: https://v4.mui.com/components/cards/ [8]: https://v4.mui.com/customization/palette/#default-values [9]: https://v4.mui.com/customization/typography/ -[10]: https://backstage.io/docs/getting-started/app-custom-theme -[11]: https://backstage.io/docs/getting-started/app-custom-theme#overriding-backstage-and-material-ui-components-styles +[10]: https://backstage.io/docs/conf/user-interface + + + [12]: https://v4.mui.com/customization/default-theme/#explore diff --git a/docs/dls/design.md b/docs/dls/design.md index 7e2ae90528..e988d1032f 100644 --- a/docs/dls/design.md +++ b/docs/dls/design.md @@ -127,7 +127,7 @@ your own plugin for Backstage. **[Discord](https://discord.gg/backstage-687207715902193673)** - all design questions should be directed to the _#design_ channel. -**[Customize Backstage's look and feel](https://backstage.io/docs/getting-started/app-custom-theme)** - +**[Customizing Your App's UI](https://backstage.io/docs/conf/user-interface)** - How to customize the look and feel of your Backstage instance by extending the theme. diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md index 7055db1138..f0823ac47e 100644 --- a/docs/features/search/how-to-guides.md +++ b/docs/features/search/how-to-guides.md @@ -116,7 +116,7 @@ indexBuilder.addCollator({ The default highlighting styling for matched terms in search results is your browsers default styles for the `` HTML tag. If you want to customize how highlighted terms look you can follow Backstage's guide on how to -[Customize the look-and-feel of your App](https://backstage.io/docs/getting-started/app-custom-theme) +[Customizing Your App's UI](https://backstage.io/docs/conf/user-interface) to create an override with your preferred styling. For example, using the new MUI V4+V5 unified theming method, the following will result diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md deleted file mode 100644 index 38f2fd4c98..0000000000 --- a/docs/getting-started/app-custom-theme.md +++ /dev/null @@ -1,613 +0,0 @@ ---- -id: app-custom-theme -title: Customize the look-and-feel of your App -description: Documentation on Customizing look and feel of the App ---- - -Backstage ships with a default theme with a light and dark mode variant. The themes are provided as a part of the [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme) package, which also includes utilities for customizing the default theme, or creating completely new themes. - -## Creating a Custom Theme - -The easiest way to create a new theme is to use the `createUnifiedTheme` function exported by the [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme) package. You can use it to override some basic parameters of the default theme such as the color palette and font. - -For example, you can create a new theme based on the default light theme like this: - -```ts title="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', -}); -``` - -:::note Note - -we recommend creating a `theme` folder in `packages/app/src` to place your theme file to keep things nicely organized. - -::: - -You can also create a theme from scratch that matches the `BackstageTheme` type exported by [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme). See the -[Material UI docs on theming](https://material-ui.com/customization/theming/) for more information about how that can be done. - -## Using your Custom Theme - -To add a custom theme to your Backstage app, you pass it as configuration to `createApp`. - -For example, adding the theme that we created in the previous section can be done like this: - -```tsx title="packages/app/src/App.tsx" -import { createApp } from '@backstage/app-defaults'; -import { ThemeProvider } from '@material-ui/core/styles'; -import CssBaseline from '@material-ui/core/CssBaseline'; -import LightIcon from '@material-ui/icons/WbSunny'; -import { UnifiedThemeProvider} from '@backstage/theme'; -import { myTheme } from './themes/myTheme'; - -const app = createApp({ - apis: ..., - plugins: ..., - themes: [{ - id: 'my-theme', - title: 'My Custom Theme', - variant: 'light', - icon: , - Provider: ({ children }) => ( - - ), - }] -}) -``` - -Note that 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`](https://www.npmjs.com/package/@backstage/theme). - -## Example of a custom theme - -```ts title="packages/app/src/theme/myTheme.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 }), - }, -}); -``` - -For a more complete example of a custom theme including Backstage and Material UI component overrides, see the [Aperture theme](https://github.com/backstage/demo/blob/master/packages/app/src/theme/aperture.ts) from the [Backstage demo site](https://demo.backstage.io). - -## Custom Typography - -When creating a custom theme you can also customize various aspects of the default typography, here's an example using simplified theme: - -```ts title="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: - -```ts title="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](https://mui.com/material-ui/customization/typography/). - -After that you can then utilize the `styleOverrides` of `MuiCssBaseline` under components to add a font to the `@font-face` array. - -```ts title="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. - -```ts title="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: - -```tsx -const useStyles = makeStyles( - 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: - -```ts title="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}`, - }), - }, - }, - }, -}); -``` - -## Custom Logo - -In addition to a custom theme, you can also customize the logo displayed at the far top left of the site. - -In your frontend app, locate `src/components/Root/` folder. You'll find two components: - -- `LogoFull.tsx` - A larger logo used when the Sidebar navigation is opened. -- `LogoIcon.tsx` - A smaller logo used when the Sidebar navigation is closed. - -To replace the images, you can simply replace the relevant code in those components with raw SVG definitions. - -You can also use another web image format such as PNG by importing it. To do this, place your new image into a new subdirectory such as `src/components/Root/logo/my-company-logo.png`, and then add this code: - -```tsx -import MyCustomLogoFull from './logo/my-company-logo.png'; - -const LogoFull = () => { - return ; -}; -``` - -## Icons - -So far you've seen how to create your own theme and add your own logo, in the following sections you'll be shown how to override the existing icons and how to add more icons - -### Custom Icons - -You can also customize the Project's _default_ icons. - -You can change the following [icons](https://github.com/backstage/backstage/blob/master/packages/app-defaults/src/defaults/icons.tsx). - -#### Requirements - -- Files in `.svg` format -- React components created for the icons - -#### Create React Component - -In your front-end application, locate the `src` folder. We suggest creating the `assets/icons` directory and `CustomIcons.tsx` file. - -```tsx title="customIcons.tsx" -import { SvgIcon, SvgIconProps } from '@material-ui/core'; - -export const ExampleIcon = (props: SvgIconProps) => ( - - - -); -``` - -#### Using the custom icon - -Supply your custom icon in `packages/app/src/App.tsx` - -```tsx title="packages/app/src/App.tsx" -/* highlight-add-next-line */ -import { ExampleIcon } from './assets/icons/CustomIcons' - - -const app = createApp({ - apis, - components: { - {/* ... */} - }, - themes: [ - {/* ... */} - ], - /* highlight-add-start */ - icons: { - github: ExampleIcon, - }, - /* highlight-add-end */ - bindRoutes({ bind }) { - {/* ... */} - } -}) -``` - -### Adding Icons - -You can add more icons, if the [default icons](https://github.com/backstage/backstage/blob/master/packages/app-defaults/src/defaults/icons.tsx) do not fit your needs, so that they can be used in other places like for Links in your entities. For this example we'll be using icons from[Material UI](https://v4.mui.com/components/material-icons/) and specifically the `AlarmIcon`. Here's how to do that: - -1. First you will want to open your `App.tsx` in `/packages/app/src` -2. Then you want to import your icon, add this to the rest of your imports: `import AlarmIcon from '@material-ui/icons/Alarm';` -3. Next you want to add the icon like this to your `createApp`: - - ```tsx title="packages/app/src/App.tsx" - const app = createApp({ - apis: ..., - plugins: ..., - /* highlight-add-start */ - icons: { - alert: AlarmIcon, - }, - /* highlight-add-end */ - themes: ..., - components: ..., - }); - ``` - -4. Now we can reference `alert` for our icon in our entity links like this: - - ```yaml - apiVersion: backstage.io/v1alpha1 - kind: Component - metadata: - name: artist-lookup - description: Artist Lookup - links: - - url: https://example.com/alert - title: Alerts - icon: alert - ``` - - And this is the result: - - ![Example Link with Alert icon](../assets/getting-started/add-icons-links-example.png) - - Another way you can use these icons is from the `AppContext` like this: - - ```ts - import { useApp } from '@backstage/core-plugin-api'; - - const app = useApp(); - const alertIcon = app.getSystemIcon('alert'); - ``` - - You might want to use this method if you have an icon you want to use in several locations. - -:::note Note - -If the icon is not available as one of the default icons or one you've added then it will fall back to Material UI's `LanguageIcon` - -::: - -## Custom Sidebar - -As you've seen there are many ways that you can customize your Backstage app. The following section will show you how you can customize the sidebar. - -### Sidebar Sub-menu - -For this example we'll show you how you can expand the sidebar with a sub-menu: - -1. Open the `Root.tsx` file located in `packages/app/src/components/Root` as this is where the sidebar code lives -2. Then we want to add the following import for `useApp`: - - ```tsx title="packages/app/src/components/Root/Root.tsx" - import { useApp } from '@backstage/core-plugin-api'; - ``` - -3. Then update the `@backstage/core-components` import like this: - - ```tsx title="packages/app/src/components/Root/Root.tsx" - import { - Sidebar, - sidebarConfig, - SidebarDivider, - SidebarGroup, - SidebarItem, - SidebarPage, - SidebarScrollWrapper, - SidebarSpace, - useSidebarOpenState, - Link, - /* highlight-add-start */ - GroupIcon, - SidebarSubmenu, - SidebarSubmenuItem, - /* highlight-add-end */ - } from '@backstage/core-components'; - ``` - -4. Finally replace `` with this: - - ```tsx title="packages/app/src/components/Root/Root.tsx" - - - - - - - - - - - - - - ``` - -When you startup your Backstage app and hover over the Home option on the sidebar you'll now see a nice sub-menu appear with links to the various Kinds in your Catalog. It would look like this: - -![Sidebar sub-menu example](./../assets/getting-started/sidebar-submenu-example.png) - -You can see more ways to use this in the [Storybook Sidebar examples](https://backstage.io/storybook/?path=/story/layout-sidebar--sample-scalable-sidebar) - -## Custom Homepage - -In addition to a custom theme, a custom logo, you can also customize the -homepage of your app. Read the full guide on the [next page](homepage.md). - -## Migrating to Material UI v5 - -We now support Material UI v5 in Backstage. Check out our [migration guide](../tutorials/migrate-to-mui5.md) to get started. diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts index 5a408d5d1f..b7e89825ca 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -35,7 +35,6 @@ export default { 'getting-started/config/database', 'getting-started/config/authentication', 'getting-started/configure-app-with-plugins', - 'getting-started/app-custom-theme', 'getting-started/homepage', ], }, @@ -398,6 +397,16 @@ export default { 'conf/reading', 'conf/writing', 'conf/defining', + { + type: 'category', + label: 'User Interface', + items: [ + 'conf/user-interface/index', + 'conf/user-interface/logo', + 'conf/user-interface/icons', + 'conf/user-interface/sidebar', + ], + }, ], Framework: [ { From f23cd8862150f64f109a6cbff2bcce9931fd67fc Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 10 Sep 2025 13:56:59 +0100 Subject: [PATCH 02/11] Fix spelling mistakes Signed-off-by: Charles de Dreuille --- docs/conf/user-interface/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf/user-interface/index.md b/docs/conf/user-interface/index.md index 9be726d2dc..5f2d355137 100644 --- a/docs/conf/user-interface/index.md +++ b/docs/conf/user-interface/index.md @@ -78,7 +78,7 @@ const app = createApp({ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `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 refered 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"`. | +| `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. | From 8b25afdf12c0cbfc54a5e5e57eedb9460bb9b874 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 11 Sep 2025 11:32:32 +0100 Subject: [PATCH 03/11] Fix link error Signed-off-by: Charles de Dreuille --- docs/getting-started/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index 252884dc54..876af4f0a5 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -157,7 +157,7 @@ Choose the correct next steps for your user role, if you're likely to be deployi - Configuring Backstage - [Adding plugins](./configure-app-with-plugins.md) - - [Customizing the theme](./app-custom-theme.md) + - [Customizing Your App's UI](../conf/user-interface/index.md) - [Populating the homepage](./homepage.md) ### Developer From d0baa442e873bfe028a7f0353f3336205f369596 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 12 Sep 2025 11:53:27 +0100 Subject: [PATCH 04/11] Add CSS variables + class name structure Signed-off-by: Charles de Dreuille --- .../css-classname-structure.png | Bin 0 -> 42743 bytes docs/conf/user-interface/index.md | 154 +++++++++++++++++- microsite/src/theme/customTheme.scss | 5 + 3 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 docs/assets/user-interface/css-classname-structure.png diff --git a/docs/assets/user-interface/css-classname-structure.png b/docs/assets/user-interface/css-classname-structure.png new file mode 100644 index 0000000000000000000000000000000000000000..c241b182cc49d9044901d7e4c0dcbb30479bf006 GIT binary patch literal 42743 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 + 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. Typeically 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 -### Custom font +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](../../assets/user-interface/css-classname-structure.png) + +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) diff --git a/microsite/src/theme/customTheme.scss b/microsite/src/theme/customTheme.scss index 707acb4073..823327b28f 100644 --- a/microsite/src/theme/customTheme.scss +++ b/microsite/src/theme/customTheme.scss @@ -171,6 +171,11 @@ html[data-theme='light'] { border-left: 3px solid rgb(255, 255, 255, 0.5); } +table { + width: 100%; + display: table; +} + /* #endregion */ /* For the docusaurus-pushfeedback plugin */ From 1dfaeeebdf23e744c4b494831102a04fc6c6eb31 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 12 Sep 2025 11:58:41 +0100 Subject: [PATCH 05/11] Update mkdocs.yml Signed-off-by: Charles de Dreuille --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index eea5fc9c40..45fdd2c9d6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -28,7 +28,7 @@ nav: - Database: 'getting-started/config/database.md' - Authentication: 'getting-started/config/authentication.md' - Configuring App with plugins: 'getting-started/configure-app-with-plugins.md' - - Customize the look-and-feel of your App: 'getting-started/app-custom-theme.md' + - Customize the look-and-feel of your App: 'conf/user-interface/index.md' - Customizing your Homepage: 'getting-started/homepage.md' - Deployment: - Deploying Backstage: 'deployment/index.md' From f5f1cece1712ffdf84f477405415db36354ab6ba Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 15 Sep 2025 11:37:57 +0100 Subject: [PATCH 06/11] Update docs/conf/user-interface/icons.md Co-authored-by: Patrik Oldsberg Signed-off-by: Charles de Dreuille --- docs/conf/user-interface/icons.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf/user-interface/icons.md b/docs/conf/user-interface/icons.md index 44b8782430..26a6f11096 100644 --- a/docs/conf/user-interface/icons.md +++ b/docs/conf/user-interface/icons.md @@ -20,7 +20,7 @@ You can change the following [icons](https://github.com/backstage/backstage/blob ### Create React Component -In your front-end application, locate the `src` folder. We suggest creating the `assets/icons` directory and `CustomIcons.tsx` file. +In your front-end application, locate the `src` folder. We suggest creating the `assets/icons` directory and `customIcons.tsx` file. ```tsx title="customIcons.tsx" import { SvgIcon, SvgIconProps } from '@material-ui/core'; From 95c442a9bdc17e52f273eb6de03604abee6882dc Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 15 Sep 2025 12:39:09 +0100 Subject: [PATCH 07/11] Docs fixes Signed-off-by: Charles de Dreuille --- docs/conf/user-interface/index.md | 2 +- microsite/docusaurus.config.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/conf/user-interface/index.md b/docs/conf/user-interface/index.md index d4ecbc6940..aeae086b59 100644 --- a/docs/conf/user-interface/index.md +++ b/docs/conf/user-interface/index.md @@ -38,7 +38,7 @@ We recognize that maintaining two separate theming systems is not ideal. Because ## 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. At this time, there is no automated solution to generate MUI themes from your Backstage UI theme, so you will need to manage both separately until the migration is complete. +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](https://github.com/backstage/backstage/pull/31140). ```tsx title="packages/app/src/App.tsx" /* highlight-add-start */ diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index d7538cd12d..83f2f75689 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -269,6 +269,10 @@ const config: Config = { from: '/docs/plugins/url-reader/', to: '/docs/backend-system/core-services/url-reader', }, + { + from: '/docs/getting-started/app-custom-theme', + to: '/docs/conf/user-interface', + } ], }), [ From f3c9a474004b78da6028517e707c00789f575bf7 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 7 Oct 2025 07:27:01 +0100 Subject: [PATCH 08/11] Small fixes Signed-off-by: Charles de Dreuille --- .github/vale/config/vocabularies/Backstage/accept.txt | 1 + docs-ui/src/app/theming/page.mdx | 2 +- docs/conf/user-interface/index.md | 2 +- docs/dls/component-design-guidelines.md | 4 ---- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index b0225be547..1497386418 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -280,6 +280,7 @@ modularization monorepo Monorepo monorepos +monospace morgan msgraph msw diff --git a/docs-ui/src/app/theming/page.mdx b/docs-ui/src/app/theming/page.mdx index 73275020bc..92b5256791 100644 --- a/docs-ui/src/app/theming/page.mdx +++ b/docs-ui/src/app/theming/page.mdx @@ -226,7 +226,7 @@ color of your app. ### Foreground colors -Foreground colours are meant to work in pair with a background colours. Typeically this would work +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. diff --git a/docs/conf/user-interface/index.md b/docs/conf/user-interface/index.md index aeae086b59..81acfdf720 100644 --- a/docs/conf/user-interface/index.md +++ b/docs/conf/user-interface/index.md @@ -178,7 +178,7 @@ These colors are used for the background of your application. We are mostly usin #### Foreground colors -Foreground colours are meant to work in pair with a background colours. Typeically 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. +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 | | ------------------------ | ----------------------------------------------------------------- | diff --git a/docs/dls/component-design-guidelines.md b/docs/dls/component-design-guidelines.md index 17e9d1f11a..fddffd9fd9 100644 --- a/docs/dls/component-design-guidelines.md +++ b/docs/dls/component-design-guidelines.md @@ -104,7 +104,3 @@ accessibility. [8]: https://v4.mui.com/customization/palette/#default-values [9]: https://v4.mui.com/customization/typography/ [10]: https://backstage.io/docs/conf/user-interface - - - -[12]: https://v4.mui.com/customization/default-theme/#explore From 74dcd32da46125c1c18d33ad8137a2e0ee784072 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 7 Oct 2025 17:45:12 +0100 Subject: [PATCH 09/11] Update sidebars.ts Signed-off-by: Charles de Dreuille --- microsite/sidebars.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts index b7e89825ca..2d5065400b 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -396,17 +396,7 @@ export default { 'conf/index', 'conf/reading', 'conf/writing', - 'conf/defining', - { - type: 'category', - label: 'User Interface', - items: [ - 'conf/user-interface/index', - 'conf/user-interface/logo', - 'conf/user-interface/icons', - 'conf/user-interface/sidebar', - ], - }, + 'conf/defining' ], Framework: [ { @@ -551,6 +541,16 @@ export default { 'tooling/package-metadata', ], }, + { + type: 'category', + label: 'User Interface', + items: [ + 'conf/user-interface/index', + 'conf/user-interface/logo', + 'conf/user-interface/icons', + 'conf/user-interface/sidebar', + ], + }, ], Tutorials: [ { 'Non-technical': ['overview/adopting'] }, From 1ac09a8060abf264d9558ceed17f7cc547779dfb Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 7 Oct 2025 18:01:11 +0100 Subject: [PATCH 10/11] Update docusaurus.config.ts Signed-off-by: Charles de Dreuille --- microsite/docusaurus.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index 83f2f75689..206dbba97f 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -272,7 +272,7 @@ const config: Config = { { from: '/docs/getting-started/app-custom-theme', to: '/docs/conf/user-interface', - } + }, ], }), [ From 79d72fa86e3dc00c4af07066ed243ed4bf0a3a16 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 7 Oct 2025 18:15:18 +0100 Subject: [PATCH 11/11] Update sidebars.ts Signed-off-by: Charles de Dreuille --- microsite/sidebars.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts index 2d5065400b..1f05660d7e 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -396,7 +396,7 @@ export default { 'conf/index', 'conf/reading', 'conf/writing', - 'conf/defining' + 'conf/defining', ], Framework: [ {