A button-styled link with primary, secondary, and tertiary variants.
import { ButtonLink } from '@backstage/ui';
<ButtonLink href="https://ui.backstage.io">Button</ButtonLink>| Prop | Type | Default | Description |
|---|---|---|---|
variant | primarysecondarytertiary | primary | Visual style. Use primary for main actions, secondary for alternatives, tertiary for low-emphasis. |
size | smallmedium | small | Link size. Use small for inline contexts, medium for standalone. |
iconStart | ReactElement | - | Icon displayed before the link text. |
iconEnd | ReactElement | - | Icon displayed after the link text. |
isDisabled | boolean | false | Prevents interaction and applies disabled styling. |
href | string | - | URL the link navigates to. |
target | _self_blank_parent_top | - | Where to open the linked URL. Use _blank for external links. |
noTrack | boolean | - | Suppresses the automatic analytics click event, e.g. if you already have custom tracking. |
onSurface | 0123dangerwarningsuccessauto | - | Surface context for correct color contrast. |
children | ReactNode | - | |
className | string | - | Additional CSS class name for custom styling. |
style | CSSProperties | - | Inline CSS styles object. |
Inherits all React Aria Link props.
<Flex align="center">
<ButtonLink size="small" href="https://ui.backstage.io" target="_blank">
Small
</ButtonLink>
<ButtonLink size="medium" href="https://ui.backstage.io" target="_blank">
Medium
</ButtonLink>
</Flex><Flex gap="4">
<ButtonLink variant="primary" isDisabled href="https://ui.backstage.io" target="_blank">
Primary
</ButtonLink>
<ButtonLink variant="secondary" isDisabled href="https://ui.backstage.io" target="_blank">
Secondary
</ButtonLink>
<ButtonLink variant="tertiary" isDisabled href="https://ui.backstage.io" target="_blank">
Tertiary
</ButtonLink>
</Flex>Our theming system is based on a mix between CSS classes, CSS variables and data attributes. If you want to customise this component, you can use one of these class names below.
bui-ButtonLinkbui-ButtonLinkContentBreaking Updated BUI links to use the hosting application's client-side router, including relative destinations and the application's configured router base path, while preserving native browser navigation where required.
Anchor-based components no longer accept the React Aria render prop. BUI now owns the underlying anchor so routing behavior remains consistent across application and plugin package versions.
ListRow, Tag, and table Row now retain client-side navigation when application and plugin packages load separate React Aria copies. Their existing modifier-key, target, download, and link-metadata behavior is unchanged. #35299
Migration Guide:
Remove render props from ButtonLink, ComboboxItem, Link, MenuItem, MenuListBoxItem, SearchAutocompleteItem, SelectItem, and Tab. BUI now selects and renders the appropriate anchor or router link automatically.
http://, https://, //, mailto:, tel:) are now passed through unchanged. Internal href values are resolved against the current basename exactly once, which also fixes a latent issue where internal link clicks under a non-root base path could navigate to a URL with the basename prefix doubled. #34004href resolution for BUI link components. Relative paths like ../other are now correctly turned into absolute paths before reaching the React Aria layer, ensuring client-side navigation goes to the right place. #33597Breaking Centralized client-side routing in BUIProvider. Components like Link, ButtonLink, Tabs, Menu, TagGroup, and Table now require a BUIProvider rendered inside a React Router context for client-side navigation to work. #33267
Migration Guide:
This change requires updating @backstage/plugin-app and @backstage/core-app-api alongside @backstage/ui. If you only upgrade @backstage/ui, BUI components will fall back to full-page navigation.
If you cannot upgrade all packages together, or if you have a custom app shell, add a BUIProvider inside your Router:
+ import { BUIProvider } from '@backstage/ui';
<BrowserRouter>
+ <BUIProvider>
<AppContent />
+ </BUIProvider>
</BrowserRouter>
Added analytics capabilities to the component library. Components with navigation behavior (Link, ButtonLink, Tab, MenuItem, Tag, Row) now fire analytics events on click when a BUIProvider is present.
New exports: BUIProvider, useAnalytics, getNodeText, and associated types (AnalyticsTracker, UseAnalyticsFn, BUIProviderProps, AnalyticsEventAttributes).
Components with analytics support now accept a noTrack prop to suppress event firing. #33150
Fixed focus ring styles to use React Aria's [data-focus-visible] data attribute instead of the native CSS :focus-visible pseudo-class. This ensures keyboard focus rings render reliably when focus is managed programmatically by React Aria (e.g. inside a GridList, Menu, or Select). #33358
Fixed handling of the style prop on Button, ButtonIcon, and ButtonLink so that it is now correctly forwarded to the underlying element instead of being silently dropped. #33095
Breaking Replaced Surface / onSurface system with new provider/consumer background system
The old Surface type ('0'–'3', 'auto') and its associated props (surface, onSurface) have been replaced by a provider/consumer bg architecture.
Types:
ContainerBg — 'neutral-1' | 'neutral-2' | 'neutral-3' | 'danger' | 'warning' | 'success'ProviderBg — ContainerBg | 'neutral-auto'Consumer components (e.g. Button) inherit the parent's bg via data-on-bg, and CSS handles the visual step-up. See "Neutral level capping" below for details on how levels are bounded.
Hooks:
useBgProvider(bg?) — for provider components. Returns { bg: undefined } when no bg is given (transparent). Supports 'neutral-auto' to auto-increment from the parent context.useBgConsumer() — for consumer components. Returns the parent container's bg unchanged.Component roles:
data-bg, wrap children in BgProvider. Transparent by default — they do not auto-increment; pass bg="neutral-auto" explicitly if you want automatic neutral stepping.data-on-bg, inherit the parent container's bg unchanged.data-bg and data-on-bg, wraps children. Card passes bg="neutral-auto" to its inner Box, so it auto-increments from the parent context.Neutral level capping:
Provider components cap at neutral-3. There is no neutral-4 prop value. The neutral-4 level exists only in consumer component CSS — for example, a Button sitting on a neutral-3 surface uses neutral-4 tokens internally via data-on-bg. #32711
Migration Guide:
Rename the surface prop to bg on provider components and update values:
- <Box surface="1">
+ <Box bg="neutral-1">
- <Card surface="2">
+ <Card bg="neutral-2">
- <Flex surface="0">
+ <Flex bg="neutral-1">
- <Grid.Root surface="1">
+ <Grid.Root bg="neutral-1">
Remove onSurface from consumer components — they now always inherit from the parent container:
- <Button onSurface="1" variant="secondary">
+ <Button variant="secondary">
- <ButtonIcon onSurface="2" variant="secondary" />
+ <ButtonIcon variant="secondary" />
- <ToggleButton onSurface="1">
+ <ToggleButton>
Update type imports:
- import type { Surface, LeafSurfaceProps, ContainerSurfaceProps } from '@backstage/ui';
+ import type { ContainerBg, ProviderBg } from '@backstage/ui';
Replace hook usage in custom components:
- import { useSurface, SurfaceProvider } from '@backstage/ui';
+ import { useBgProvider, useBgConsumer, BgProvider } from '@backstage/ui';
- const { surface } = useSurface({ surface: props.surface });
+ const { bg } = useBgProvider(props.bg);
- const { surface } = useSurface({ onSurface: props.onSurface });
+ const { bg } = useBgConsumer();
Update CSS selectors targeting surface data attributes:
- [data-surface='1'] { ... }
+ [data-bg='neutral-1'] { ... }
- [data-on-surface='1'] { ... }
+ [data-on-bg='neutral-1'] { ... }
Note: Provider components use data-bg (values: neutral-1 through neutral-3, plus intent values). Consumer components use data-on-bg, which reflects the parent container's bg directly. The neutral-4 level never appears as a prop or data-bg value — it is used only in consumer CSS.
Breaking Changed CSS selectors for ButtonIcon and ButtonLink components. Custom styles targeting .bui-Button to style these components must be updated to use .bui-ButtonIcon or .bui-ButtonLink respectively.
-/* This no longer styles ButtonIcon or ButtonLink */
-.bui-Button[data-variant="primary"] { ... }
+/* Use component-specific selectors */
+.bui-ButtonIcon[data-variant="primary"] { ... }
+.bui-ButtonLink[data-variant="primary"] { ... }
``` [#32308](https://github.com/backstage/backstage/pull/32308)