Improve docs + container props on Grid + Flex

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2026-02-10 11:08:06 +00:00
parent c0183daf87
commit 46a9adc246
8 changed files with 66 additions and 78 deletions
+12
View File
@@ -0,0 +1,12 @@
---
'@backstage/ui': minor
---
**BREAKING**: Alert no longer accepts a `surface` prop
The Alert component's background is now driven entirely by its `status` prop. The `surface` prop has been removed.
```diff
- <Alert surface="1" status="info" />
+ <Alert status="info" />
```
+5 -12
View File
@@ -4,7 +4,7 @@
**BREAKING**: Replaced `Surface` / `onSurface` system with new `ContainerBg` background system
The old `Surface` type (`'0'``'3'`, `'auto'`) and its associated props (`surface`, `onSurface`) have been replaced by a new `ContainerBg` type with semantic neutral levels (`'neutral-1'` through `'neutral-3'`) and intents (`'danger'`, `'warning'`, `'success'`). Containers are capped at `neutral-3`; the `neutral-4` level is reserved for leaf component CSS only. Leaf components like Button no longer need an explicit prop — they receive a `data-on-bg` attribute matching the parent container's bg, and CSS handles the visual step-up.
The old `Surface` type (`'0'``'3'`, `'auto'`) and its associated props (`surface`, `onSurface`) have been replaced by `ContainerBg` — a union of `'neutral-1'` | `'neutral-2'` | `'neutral-3'` | `'danger'` | `'warning'` | `'success'`. There is no `neutral-4` value; containers are capped at `neutral-3`. Leaf components like Button no longer accept a bg prop — they inherit the parent container's bg via a `data-on-bg` attribute, and CSS handles the visual step-up to the next neutral level.
New `useBg` hook and `BgProvider` replace the deleted `useSurface` hook and `SurfaceProvider`.
@@ -26,7 +26,7 @@ Rename the `surface` prop to `bg` on container components and update values:
+ <Grid.Root bg="neutral-1">
```
Remove `onSurface` from leaf components — it is now fully automatic:
Remove `onSurface` from leaf components — they now always inherit from the parent container and can no longer override the value:
```diff
- <Button onSurface="1" variant="secondary">
@@ -39,13 +39,6 @@ Remove `onSurface` from leaf components — it is now fully automatic:
+ <ToggleButton>
```
Alert no longer accepts a `surface` prop (its background is driven by `status`):
```diff
- <Alert surface="1" status="info" />
+ <Alert status="info" />
```
Update type imports:
```diff
@@ -72,10 +65,10 @@ Update CSS selectors targeting surface data attributes:
- [data-surface='1'] { ... }
+ [data-bg='neutral-1'] { ... }
- [data-on-surface='2'] { ... }
- [data-on-surface='1'] { ... }
+ [data-on-bg='neutral-1'] { ... }
```
Note: Container components use `data-bg` for their own background level. Leaf components now use `data-on-bg` which reflects the parent container's bg (not an auto-incremented value).
Note: Container components use `data-bg` (values: `neutral-1` through `neutral-3`, plus intents). Leaf components use `data-on-bg`, which reflects the parent container's bg directly (no auto-increment).
**Affected components:** Box, Button, ButtonIcon, ButtonLink, ToggleButton, Card, Alert, Flex, Grid
**Affected components:** Box, Button, ButtonIcon, ButtonLink, ToggleButton, Card, Flex, Grid
@@ -282,21 +282,16 @@ export const BgAutoIncrement = meta.story({
render: args => (
<Flex direction="column">
<div style={{ maxWidth: '600px', marginBottom: '16px' }}>
Nested Flex components automatically increment their neutral background
level. No explicit bg prop is needed on inner Flex components.
Flex is a layout primitive and is transparent to the bg system by
default. Only an explicit bg prop establishes a new bg level. Nested
Flex components without a bg prop inherit the parent context unchanged.
</div>
<Flex {...args} bg="neutral-1" direction="column">
<div>Neutral 1 (explicit)</div>
<Flex {...args} direction="column">
<div>Auto (becomes neutral-2)</div>
<Flex {...args} direction="column">
<div>Auto (becomes neutral-3)</div>
<Flex {...args} direction="column">
<div>Auto (becomes neutral-4)</div>
<Flex {...args} direction="column">
<div>Auto (stays neutral-4 - capped)</div>
</Flex>
</Flex>
<Flex {...args} bg="neutral-2" direction="column">
<div>Neutral 2 (explicit)</div>
<Flex {...args} bg="neutral-3" direction="column">
<div>Neutral 3 (explicit, capped)</div>
</Flex>
</Flex>
</Flex>
+5 -2
View File
@@ -24,8 +24,11 @@ import { BgProvider, useBg } from '../../hooks/useBg';
/** @public */
export const Flex = forwardRef<HTMLDivElement, FlexProps>((props, ref) => {
// Resolve the bg this Flex creates for its children
const { bg: resolvedBg } = useBg({ mode: 'container', bg: props.bg });
// Only establish bg context when an explicit bg prop is provided.
// Flex is a layout primitive — it should be transparent to the bg system by default.
const { bg: resolvedBg } = useBg(
props.bg !== undefined ? { mode: 'container', bg: props.bg } : undefined,
);
const { classNames, dataAttributes, utilityClasses, style, cleanedProps } =
useStyles(FlexDefinition, {
@@ -182,15 +182,16 @@ export const BgAutoIncrement = meta.story({
render: args => (
<Flex direction="column">
<div style={{ maxWidth: '600px', marginBottom: '16px' }}>
Nested Grid components automatically increment their neutral background.
Each nested Grid.Item inherits and increments from its parent's bg.
Grid is a layout primitive and is transparent to the bg system by
default. Only an explicit bg prop establishes a new bg level. Nested
grids without a bg prop inherit the parent context unchanged.
</div>
<Grid.Root {...args} bg="neutral-1">
<Grid.Item>Neutral 1 (Grid.Root)</Grid.Item>
<Grid.Item>
<Grid.Root {...args}>
<Grid.Item>Nested: Auto (becomes neutral-2)</Grid.Item>
<Grid.Item>Nested: Auto (becomes neutral-2)</Grid.Item>
<Grid.Root {...args} bg="neutral-2">
<Grid.Item>Nested: neutral-2 (explicit)</Grid.Item>
<Grid.Item>Nested: neutral-2 (explicit)</Grid.Item>
</Grid.Root>
</Grid.Item>
</Grid.Root>
+10 -4
View File
@@ -23,8 +23,11 @@ import styles from './Grid.module.css';
import { BgProvider, useBg } from '../../hooks/useBg';
const GridRoot = forwardRef<HTMLDivElement, GridProps>((props, ref) => {
// Resolve the bg this Grid creates for its children
const { bg: resolvedBg } = useBg({ mode: 'container', bg: props.bg });
// Only establish bg context when an explicit bg prop is provided.
// Grid is a layout primitive — it should be transparent to the bg system by default.
const { bg: resolvedBg } = useBg(
props.bg !== undefined ? { mode: 'container', bg: props.bg } : undefined,
);
const { classNames, dataAttributes, utilityClasses, style, cleanedProps } =
useStyles(GridDefinition, {
@@ -59,8 +62,11 @@ const GridRoot = forwardRef<HTMLDivElement, GridProps>((props, ref) => {
});
const GridItem = forwardRef<HTMLDivElement, GridItemProps>((props, ref) => {
// Resolve the bg this GridItem creates for its children
const { bg: resolvedBg } = useBg({ mode: 'container', bg: props.bg });
// Only establish bg context when an explicit bg prop is provided.
// GridItem is a layout slot — it should be transparent to the bg system by default.
const { bg: resolvedBg } = useBg(
props.bg !== undefined ? { mode: 'container', bg: props.bg } : undefined,
);
const { classNames, dataAttributes, utilityClasses, style, cleanedProps } =
useStyles(GridItemDefinition, {
@@ -21,7 +21,6 @@ import type { ToggleButtonProps } from './types';
import { useStyles } from '../../hooks/useStyles';
import { ToggleButtonDefinition } from './definition';
import styles from './ToggleButton.module.css';
import { useBg } from '../../hooks/useBg';
/** @public */
export const ToggleButton = forwardRef(
@@ -36,14 +35,11 @@ export const ToggleButton = forwardRef(
const { children, className, iconStart, iconEnd, ...rest } = cleanedProps;
const { bg } = useBg({ mode: 'leaf' });
return (
<AriaToggleButton
className={clsx(classNames.root, styles[classNames.root], className)}
ref={ref}
{...dataAttributes}
{...(bg ? { 'data-on-bg': bg } : {})}
{...rest}
>
{renderProps => {
+21 -39
View File
@@ -62,11 +62,9 @@ const BgContext = createVersionedContext<{
* Increments a neutral bg level by one, capping at 'neutral-3'.
* Intent backgrounds (danger, warning, success) pass through unchanged.
*
* Only used by container components for auto-increment. The 'neutral-4'
* level is reserved for leaf components and is never set on containers.
* The 'neutral-4' level is reserved for leaf component CSS and is never
* set on containers.
*
* @param bg - The current bg value
* @returns The incremented bg value
* @internal
*/
function incrementNeutralBg(
@@ -81,44 +79,29 @@ function incrementNeutralBg(
}
/**
* Resolves the bg value for a container component.
* Resolves the bg for a container component.
*
* - If an explicit bg prop is provided, use that.
* - If no bg prop is provided but there is a parent context, auto-increment from parent.
* - If no bg prop and no context, return undefined (no bg).
* Uses the explicit `bg` prop if provided. Otherwise auto-increments from
* the parent context, capping at `neutral-3`. Returns undefined when there
* is no prop and no parent context.
*
* @internal
*/
function resolveBgForContainer(
contextBg: ContainerBg | undefined,
function resolveContainerBg(
context: BgContextValue,
propBg: ContainerBg | undefined,
): ContainerBg | undefined {
): BgContextValue {
// Explicit bg prop takes priority
if (propBg !== undefined) {
return propBg;
return { bg: propBg };
}
// No explicit bg: auto-increment from context if available
if (contextBg === undefined) {
return undefined;
if (context.bg === undefined) {
return { bg: undefined };
}
return incrementNeutralBg(contextBg);
}
/**
* Resolves the bg value for a leaf component.
*
* Returns the parent context bg unchanged. The leaf component's CSS
* handles the visual step-up (e.g. on neutral-1 surface → use neutral-2 tokens).
* If no context, returns undefined.
*
* @internal
*/
function resolveBgForLeaf(
contextBg: ContainerBg | undefined,
): ContainerBg | undefined {
return contextBg;
return { bg: incrementNeutralBg(context.bg) };
}
/**
@@ -137,9 +120,6 @@ export const BgProvider = ({ bg, children }: BgProviderProps) => {
/**
* Hook to access and resolve the current bg context.
*
* All bg resolution logic lives here — callers only need to specify the mode
* and (for containers) the explicit bg prop value.
*
* - **Container mode** — uses explicit `bg` if provided, otherwise auto-increments
* from parent context. Caps at `neutral-3`.
* - **Leaf mode** — returns the parent context bg unchanged. No prop needed.
@@ -157,15 +137,17 @@ export const useBg = (options?: UseBgOptions): BgContextValue => {
return context;
}
// Leaf mode: return the parent context bg unchanged.
// The leaf component's CSS handles the visual step-up.
if (options.mode === 'leaf') {
return context;
}
// Resolve responsive prop value to a scalar for the current breakpoint
const resolvedPropBg =
const propBg =
options.bg !== undefined
? resolveResponsiveValue(options.bg, breakpoint)
: undefined;
if (options.mode === 'leaf') {
return { bg: resolveBgForLeaf(context.bg) };
}
return { bg: resolveBgForContainer(context.bg, resolvedPropBg) };
return resolveContainerBg(context, propBg);
};