From 753471e381192ac6a900eeb30ef2eaeb684a4f4c Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Wed, 15 Apr 2026 20:21:06 +0100 Subject: [PATCH 01/17] ui: add flex item related types and helper classes Also includes a solution for transforming input values for specific classes, to allow transforming e.g. "flex: true" to "flex: 1". Co-authored-by: Claude Opus 4.6 (1M context) Signed-off-by: MT Lewis --- packages/ui/src/css/utilities/flex.css | 72 +++++++++++++++++++ .../ui/src/hooks/useDefinition/helpers.ts | 7 +- packages/ui/src/types.ts | 7 ++ packages/ui/src/utils/utilityClassMap.ts | 24 ++++++- 4 files changed, 108 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/css/utilities/flex.css b/packages/ui/src/css/utilities/flex.css index 2248addfee..bf5bf392cd 100644 --- a/packages/ui/src/css/utilities/flex.css +++ b/packages/ui/src/css/utilities/flex.css @@ -67,6 +67,18 @@ flex-direction: column-reverse; } + .bui-grow { + flex-grow: var(--grow); + } + + .bui-shrink { + flex-shrink: var(--shrink); + } + + .bui-basis { + flex-basis: var(--basis); + } + /* Breakpoint xs */ @media (min-width: 640px) { .xs\:bui-align-start { @@ -116,6 +128,18 @@ .xs\:bui-fd-column-reverse { flex-direction: column-reverse; } + + .xs\:bui-grow { + flex-grow: var(--grow-xs); + } + + .xs\:bui-shrink { + flex-shrink: var(--shrink-xs); + } + + .xs\:bui-basis { + flex-basis: var(--basis-xs); + } } /* Breakpoint sm */ @@ -167,6 +191,18 @@ .sm\:bui-fd-column-reverse { flex-direction: column-reverse; } + + .sm\:bui-grow { + flex-grow: var(--grow-sm); + } + + .sm\:bui-shrink { + flex-shrink: var(--shrink-sm); + } + + .sm\:bui-basis { + flex-basis: var(--basis-sm); + } } /* Breakpoint md */ @@ -218,6 +254,18 @@ .md\:bui-fd-column-reverse { flex-direction: column-reverse; } + + .md\:bui-grow { + flex-grow: var(--grow-md); + } + + .md\:bui-shrink { + flex-shrink: var(--shrink-md); + } + + .md\:bui-basis { + flex-basis: var(--basis-md); + } } /* Breakpoint lg */ @@ -269,6 +317,18 @@ .lg\:bui-fd-column-reverse { flex-direction: column-reverse; } + + .lg\:bui-grow { + flex-grow: var(--grow-lg); + } + + .lg\:bui-shrink { + flex-shrink: var(--shrink-lg); + } + + .lg\:bui-basis { + flex-basis: var(--basis-lg); + } } /* Breakpoint xl */ @@ -320,5 +380,17 @@ .xl\:bui-fd-column-reverse { flex-direction: column-reverse; } + + .xl\:bui-grow { + flex-grow: var(--grow-xl); + } + + .xl\:bui-shrink { + flex-shrink: var(--shrink-xl); + } + + .xl\:bui-basis { + flex-basis: var(--basis-xl); + } } } diff --git a/packages/ui/src/hooks/useDefinition/helpers.ts b/packages/ui/src/hooks/useDefinition/helpers.ts index 8a990b4b6a..2169a26238 100644 --- a/packages/ui/src/hooks/useDefinition/helpers.ts +++ b/packages/ui/src/hooks/useDefinition/helpers.ts @@ -102,7 +102,7 @@ export function processUtilityProps( const handleUtilityValue = ( key: string, - val: unknown, + inputVal: unknown, prefix: string = '', ) => { // Get utility class configuration for this key @@ -113,6 +113,11 @@ export function processUtilityProps( return; } + const val = + 'transform' in utilityConfig + ? utilityConfig.transform(inputVal) + : inputVal; + // Check if value is in the list of valid values for this utility const values = utilityConfig.values as readonly (string | number)[]; if (values.length > 0 && values.includes(val as string | number)) { diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index 113eb06a5c..4c64c2fe25 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -120,6 +120,13 @@ export interface PaddingProps { /** @public */ export interface SpaceProps extends MarginProps, PaddingProps {} +/** @public */ +export interface FlexItemProps { + grow?: Responsive; + shrink?: Responsive; + basis?: Responsive; +} + /** @public */ export type TextVariants = | 'title-large' diff --git a/packages/ui/src/utils/utilityClassMap.ts b/packages/ui/src/utils/utilityClassMap.ts index f70052d588..376b9fb40c 100644 --- a/packages/ui/src/utils/utilityClassMap.ts +++ b/packages/ui/src/utils/utilityClassMap.ts @@ -196,7 +196,29 @@ export const utilityClassMap = { class: 'bui-row-span', values: columnsValues, }, + grow: { + class: 'bui-grow', + cssVar: '--grow', + values: [], + transform: input => (typeof input === 'boolean' ? Number(input) : input), + }, + shrink: { + class: 'bui-shrink', + cssVar: '--shrink', + values: [], + transform: input => (typeof input === 'boolean' ? Number(input) : input), + }, + basis: { + class: 'bui-basis', + cssVar: '--basis', + values: [], + }, } as const satisfies Record< string, - { class: string; cssVar?: string; values: readonly (string | number)[] } + { + class: string; + cssVar?: string; + values: readonly (string | number)[]; + transform?: (input: unknown) => unknown; + } >; From 058012eae8a972c72f076e535dcc0a5d71b70c15 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Thu, 16 Apr 2026 12:52:37 +0100 Subject: [PATCH 02/17] ui: add support for flex item props to Box, Card, Flex, and Grid Signed-off-by: MT Lewis --- packages/ui/src/components/Box/definition.ts | 3 +++ packages/ui/src/components/Box/types.ts | 8 +++++++- packages/ui/src/components/Card/Card.tsx | 3 ++- packages/ui/src/components/Card/definition.ts | 2 ++ packages/ui/src/components/Card/types.ts | 7 ++++++- packages/ui/src/components/Flex/definition.ts | 3 +++ packages/ui/src/components/Flex/types.ts | 9 ++++++++- packages/ui/src/components/Grid/definition.ts | 3 +++ packages/ui/src/components/Grid/types.ts | 2 ++ 9 files changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/Box/definition.ts b/packages/ui/src/components/Box/definition.ts index 3837dc8ea4..c3a38326b2 100644 --- a/packages/ui/src/components/Box/definition.ts +++ b/packages/ui/src/components/Box/definition.ts @@ -52,6 +52,9 @@ export const BoxDefinition = defineComponent()({ 'py', 'position', 'display', + 'grow', + 'shrink', + 'basis', 'width', 'minWidth', 'maxWidth', diff --git a/packages/ui/src/components/Box/types.ts b/packages/ui/src/components/Box/types.ts index 1b4259099b..e333690e2e 100644 --- a/packages/ui/src/components/Box/types.ts +++ b/packages/ui/src/components/Box/types.ts @@ -15,7 +15,12 @@ */ import type { ReactNode, CSSProperties } from 'react'; -import type { Responsive, ProviderBg, SpaceProps } from '../../types'; +import type { + Responsive, + ProviderBg, + SpaceProps, + FlexItemProps, +} from '../../types'; /** @public */ export type BoxOwnProps = { @@ -43,6 +48,7 @@ export type BoxUtilityProps = { /** @public */ export interface BoxProps extends SpaceProps, + FlexItemProps, BoxOwnProps, BoxUtilityProps, Omit, 'children'> {} diff --git a/packages/ui/src/components/Card/Card.tsx b/packages/ui/src/components/Card/Card.tsx index aecca09512..6b46980c90 100644 --- a/packages/ui/src/components/Card/Card.tsx +++ b/packages/ui/src/components/Card/Card.tsx @@ -41,7 +41,7 @@ const INTERACTIVE_ELEMENT_SELECTOR = * @public */ export const Card = forwardRef((props, ref) => { - const { ownProps, restProps, dataAttributes } = useDefinition( + const { ownProps, restProps, dataAttributes, utilityStyle } = useDefinition( CardDefinition, props, ); @@ -98,6 +98,7 @@ export const Card = forwardRef((props, ref) => { {...dataAttributes} {...restProps} onClick={isInteractive ? handleClick : undefined} + style={{ ...ownProps.style, ...utilityStyle }} > {href && ( ()({ target: {}, rel: {}, download: {}, + style: {}, }, + utilityProps: ['grow', 'shrink', 'basis'], }); /** diff --git a/packages/ui/src/components/Card/types.ts b/packages/ui/src/components/Card/types.ts index 5809384c16..08fb906058 100644 --- a/packages/ui/src/components/Card/types.ts +++ b/packages/ui/src/components/Card/types.ts @@ -16,9 +16,13 @@ import type { ReactNode } from 'react'; import type { ButtonProps as RAButtonProps } from 'react-aria-components'; +import type { FlexItemProps } from '../../types'; /** @public */ -export type CardBaseProps = { children?: ReactNode; className?: string }; +export type CardBaseProps = { + children?: ReactNode; + className?: string; +}; /** @public */ export type CardButtonVariant = { @@ -63,6 +67,7 @@ export type CardStaticVariant = { * @public */ export type CardProps = CardBaseProps & + FlexItemProps & Omit, 'onClick'> & (CardButtonVariant | CardLinkVariant | CardStaticVariant); diff --git a/packages/ui/src/components/Flex/definition.ts b/packages/ui/src/components/Flex/definition.ts index 802464c4e1..1bc9c54217 100644 --- a/packages/ui/src/components/Flex/definition.ts +++ b/packages/ui/src/components/Flex/definition.ts @@ -53,5 +53,8 @@ export const FlexDefinition = defineComponent()({ 'align', 'justify', 'direction', + 'grow', + 'shrink', + 'basis', ], }); diff --git a/packages/ui/src/components/Flex/types.ts b/packages/ui/src/components/Flex/types.ts index 1c7f887e62..5b295e1825 100644 --- a/packages/ui/src/components/Flex/types.ts +++ b/packages/ui/src/components/Flex/types.ts @@ -15,7 +15,13 @@ */ import type { ReactNode, CSSProperties } from 'react'; -import type { Responsive, Space, SpaceProps, ProviderBg } from '../../types'; +import type { + Responsive, + Space, + SpaceProps, + ProviderBg, + FlexItemProps, +} from '../../types'; /** @public */ export type FlexOwnProps = { @@ -28,6 +34,7 @@ export type FlexOwnProps = { /** @public */ export interface FlexProps extends SpaceProps, + FlexItemProps, FlexOwnProps, Omit, 'children'> { gap?: Responsive; diff --git a/packages/ui/src/components/Grid/definition.ts b/packages/ui/src/components/Grid/definition.ts index 606b286b3c..2d83e9db6a 100644 --- a/packages/ui/src/components/Grid/definition.ts +++ b/packages/ui/src/components/Grid/definition.ts @@ -51,6 +51,9 @@ export const GridDefinition = defineComponent()({ 'pt', 'px', 'py', + 'grow', + 'shrink', + 'basis', ], }); diff --git a/packages/ui/src/components/Grid/types.ts b/packages/ui/src/components/Grid/types.ts index 6ad5d564c6..90699c59d2 100644 --- a/packages/ui/src/components/Grid/types.ts +++ b/packages/ui/src/components/Grid/types.ts @@ -21,6 +21,7 @@ import type { Responsive, Columns, ProviderBg, + FlexItemProps, } from '../../types'; /** @public */ @@ -34,6 +35,7 @@ export type GridOwnProps = { /** @public */ export interface GridProps extends SpaceProps, + FlexItemProps, GridOwnProps, Omit, 'children'> { columns?: Responsive; From 60224fb0fd0668503920c810172b3547a23bb0be Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Thu, 16 Apr 2026 12:53:40 +0100 Subject: [PATCH 03/17] ui: add story for testing flex-item props to Flex storybook Signed-off-by: MT Lewis --- .../ui/src/components/Flex/Flex.stories.tsx | 135 ++++++++++++++++-- 1 file changed, 125 insertions(+), 10 deletions(-) diff --git a/packages/ui/src/components/Flex/Flex.stories.tsx b/packages/ui/src/components/Flex/Flex.stories.tsx index 91d4088004..940c71f272 100644 --- a/packages/ui/src/components/Flex/Flex.stories.tsx +++ b/packages/ui/src/components/Flex/Flex.stories.tsx @@ -16,7 +16,9 @@ import preview from '../../../../../.storybook/preview'; import { Flex } from './Flex'; import { Text } from '../Text'; -import { Box } from '../Box'; +import { Box, BoxProps } from '../Box'; +import { Card, CardHeader, CardBody, CardFooter } from '../Card'; +import { Grid } from '../Grid'; const meta = preview.meta({ title: 'Backstage UI/Flex', @@ -38,13 +40,7 @@ const meta = preview.meta({ args: { children: null }, }); -const DecorativeBox = ({ - width = '48px', - height = '48px', -}: { - width?: string; - height?: string; -}) => { +const DecorativeBox = (props: Omit) => { const diagonalStripePattern = (() => { const svg = ` @@ -58,8 +54,8 @@ const DecorativeBox = ({ return ( ); @@ -199,6 +196,124 @@ export const ResponsiveAlign = meta.story({ ), }); +export const FlexItems = meta.story({ + args: { + component: 'Box', + grow: 1, + shrink: 0, + basis: 'auto', + }, + argTypes: { + component: { + control: { type: 'select' }, + options: ['Box', 'Card', 'Grid', 'Flex'], + mapping: { + Box: props => , + Card: props => ( + + + Header + + + + This is the first paragraph of a long body text that + demonstrates how the Card component handles extensive content. + The card should adjust accordingly to display all the text + properly while maintaining its structure. + + + Here's a second paragraph that adds more content to our card + body. Having multiple paragraphs helps to visualize how spacing + works within the card component. + + + This third paragraph continues to add more text to ensure we + have a proper demonstration of a card with significant content. + This makes it easier to test scrolling behavior and overall + layout when content exceeds the initial view. + + + + Footer + + + ), + Grid: props => ( + + + + + + + + + + + + ), + Flex: props => ( + + + + + + ), + }, + }, + grow: { + control: 'radio', + options: [undefined, 0, 1, false, true], + }, + shrink: { + control: 'radio', + options: [undefined, 0, 1, false, true], + }, + basis: { + control: 'radio', + options: [undefined, '0%', '25%', '50%', '100%', 'auto'], + }, + }, + render: args => { + const Component = args.component; + + return ( + +
+ + + +
+ + ); + }, +}); + export const ResponsiveGap = meta.story({ args: { gap: { xs: '4', md: '8', lg: '12' }, From 68cf3f113cacaf5727ac15785ab1596e0c39779f Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Thu, 16 Apr 2026 12:59:49 +0100 Subject: [PATCH 04/17] ui: update api-report with new flex item props Signed-off-by: MT Lewis --- packages/ui/report.api.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 4856387abf..8a2d8ecddf 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -431,6 +431,9 @@ export const BoxDefinition: { 'py', 'position', 'display', + 'grow', + 'shrink', + 'basis', 'width', 'minWidth', 'maxWidth', @@ -452,6 +455,7 @@ export type BoxOwnProps = { // @public (undocumented) export interface BoxProps extends SpaceProps, + FlexItemProps, BoxOwnProps, BoxUtilityProps, Omit, 'children'> {} @@ -702,7 +706,9 @@ export const CardDefinition: { readonly target: {}; readonly rel: {}; readonly download: {}; + readonly style: {}; }; + readonly utilityProps: readonly ['grow', 'shrink', 'basis']; }; // @public @@ -790,6 +796,7 @@ export type CardOwnProps = Pick< // @public export type CardProps = CardBaseProps & + FlexItemProps & Omit, 'onClick'> & (CardButtonVariant | CardLinkVariant | CardStaticVariant); @@ -1315,12 +1322,25 @@ export const FlexDefinition: { 'align', 'justify', 'direction', + 'grow', + 'shrink', + 'basis', ]; }; // @public (undocumented) export type FlexDirection = 'row' | 'column'; +// @public (undocumented) +export interface FlexItemProps { + // (undocumented) + basis?: Responsive; + // (undocumented) + grow?: Responsive; + // (undocumented) + shrink?: Responsive; +} + // @public (undocumented) export type FlexOwnProps = { children: ReactNode; @@ -1332,6 +1352,7 @@ export type FlexOwnProps = { // @public (undocumented) export interface FlexProps extends SpaceProps, + FlexItemProps, FlexOwnProps, Omit, 'children'> { // (undocumented) @@ -1422,6 +1443,9 @@ export const GridDefinition: { 'pt', 'px', 'py', + 'grow', + 'shrink', + 'basis', ]; }; @@ -1478,6 +1502,7 @@ export type GridOwnProps = { // @public (undocumented) export interface GridProps extends SpaceProps, + FlexItemProps, GridOwnProps, Omit, 'children'> { // (undocumented) From a2814693fa08a6b6f2e3b0c27131c0ebb0a580f9 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Thu, 16 Apr 2026 14:12:13 +0100 Subject: [PATCH 05/17] ui: add changeset for new flex-item props Signed-off-by: MT Lewis --- .changeset/funny-areas-rescue.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/funny-areas-rescue.md diff --git a/.changeset/funny-areas-rescue.md b/.changeset/funny-areas-rescue.md new file mode 100644 index 0000000000..1e8a107814 --- /dev/null +++ b/.changeset/funny-areas-rescue.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': minor +--- + +Add support for flex item props (`grow`, `shrink`, and `basis`) to `Box`, `Card`, `Grid`, and `Flex` itself. From a344f12bc7a238e257fe5f52b34de3fafb58eabe Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Thu, 16 Apr 2026 15:14:35 +0100 Subject: [PATCH 06/17] ui: avoid passing `component` prop to rendered component in Flex story Signed-off-by: MT Lewis --- packages/ui/src/components/Flex/Flex.stories.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/ui/src/components/Flex/Flex.stories.tsx b/packages/ui/src/components/Flex/Flex.stories.tsx index 940c71f272..dac4f530c1 100644 --- a/packages/ui/src/components/Flex/Flex.stories.tsx +++ b/packages/ui/src/components/Flex/Flex.stories.tsx @@ -283,9 +283,7 @@ export const FlexItems = meta.story({ options: [undefined, '0%', '25%', '50%', '100%', 'auto'], }, }, - render: args => { - const Component = args.component; - + render: ({ component: Component, ...args }) => { return (
Date: Thu, 16 Apr 2026 15:20:58 +0100 Subject: [PATCH 07/17] ui: add `style` prop to CardBaseProps Signed-off-by: MT Lewis --- packages/ui/report.api.md | 1 + packages/ui/src/components/Card/types.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 8a2d8ecddf..66606652ad 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -646,6 +646,7 @@ export const Card: ForwardRefExoticComponent< export type CardBaseProps = { children?: ReactNode; className?: string; + style?: CSSProperties; }; // @public diff --git a/packages/ui/src/components/Card/types.ts b/packages/ui/src/components/Card/types.ts index 08fb906058..c874ec8720 100644 --- a/packages/ui/src/components/Card/types.ts +++ b/packages/ui/src/components/Card/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { ReactNode } from 'react'; +import type { ReactNode, CSSProperties } from 'react'; import type { ButtonProps as RAButtonProps } from 'react-aria-components'; import type { FlexItemProps } from '../../types'; @@ -22,6 +22,7 @@ import type { FlexItemProps } from '../../types'; export type CardBaseProps = { children?: ReactNode; className?: string; + style?: CSSProperties; }; /** @public */ From 36aff3305c43af67b0119f668351896fb071e8aa Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Thu, 16 Apr 2026 15:36:33 +0100 Subject: [PATCH 08/17] ui: clean up prop spreading in Flex story DecorativeBox component Signed-off-by: MT Lewis --- packages/ui/src/components/Flex/Flex.stories.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/Flex/Flex.stories.tsx b/packages/ui/src/components/Flex/Flex.stories.tsx index dac4f530c1..7812e47770 100644 --- a/packages/ui/src/components/Flex/Flex.stories.tsx +++ b/packages/ui/src/components/Flex/Flex.stories.tsx @@ -40,7 +40,12 @@ const meta = preview.meta({ args: { children: null }, }); -const DecorativeBox = (props: Omit) => { +const DecorativeBox = ({ + width = '48px', + height = '48px', + style, + ...props +}: Omit) => { const diagonalStripePattern = (() => { const svg = ` @@ -54,9 +59,11 @@ const DecorativeBox = (props: Omit) => { return ( ) => { fontWeight: 'bold', color: '#2563eb', }} - {...props} children={null} /> ); From 61f1276e90236577743ca8b7ad8b1e963510567a Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 17 Apr 2026 09:25:15 +0100 Subject: [PATCH 09/17] ui: style overrides take higher precedence than utility styles in Card Signed-off-by: MT Lewis --- packages/ui/src/components/Card/Card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Card/Card.tsx b/packages/ui/src/components/Card/Card.tsx index 6b46980c90..498fd5b021 100644 --- a/packages/ui/src/components/Card/Card.tsx +++ b/packages/ui/src/components/Card/Card.tsx @@ -98,7 +98,7 @@ export const Card = forwardRef((props, ref) => { {...dataAttributes} {...restProps} onClick={isInteractive ? handleClick : undefined} - style={{ ...ownProps.style, ...utilityStyle }} + style={{ ...utilityStyle, ...ownProps.style }} > {href && ( Date: Fri, 17 Apr 2026 09:46:17 +0100 Subject: [PATCH 10/17] ui: allow all valid flex-basis values for `basis` prop Signed-off-by: MT Lewis --- packages/ui/report.api.md | 4 ++-- packages/ui/src/types.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 66606652ad..57cc622161 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -13,7 +13,7 @@ import type { ColumnStaticSize } from 'react-stately'; import type { ComponentProps } from 'react'; import type { ComponentPropsWithoutRef } from 'react'; import type { ComponentPropsWithRef } from 'react'; -import type { CSSProperties } from 'react'; +import { CSSProperties } from 'react'; import type { DialogTriggerProps as DialogTriggerProps_2 } from 'react-aria-components'; import type { DisclosureGroupProps } from 'react-aria-components'; import type { DisclosurePanelProps } from 'react-aria-components'; @@ -1335,7 +1335,7 @@ export type FlexDirection = 'row' | 'column'; // @public (undocumented) export interface FlexItemProps { // (undocumented) - basis?: Responsive; + basis?: Responsive; // (undocumented) grow?: Responsive; // (undocumented) diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index 4c64c2fe25..e8ebfd654b 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { CSSProperties } from 'react'; + /** @public */ export type Breakpoint = 'initial' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; @@ -124,7 +126,7 @@ export interface SpaceProps extends MarginProps, PaddingProps {} export interface FlexItemProps { grow?: Responsive; shrink?: Responsive; - basis?: Responsive; + basis?: Responsive; } /** @public */ From 76759067a25d80af5efb2e042505bc23b75b088d Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 17 Apr 2026 10:06:23 +0100 Subject: [PATCH 11/17] ui: use type import for CSSProperties in shared types Signed-off-by: MT Lewis --- packages/ui/report.api.md | 2 +- packages/ui/src/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 57cc622161..04a146ce2c 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -13,7 +13,7 @@ import type { ColumnStaticSize } from 'react-stately'; import type { ComponentProps } from 'react'; import type { ComponentPropsWithoutRef } from 'react'; import type { ComponentPropsWithRef } from 'react'; -import { CSSProperties } from 'react'; +import type { CSSProperties } from 'react'; import type { DialogTriggerProps as DialogTriggerProps_2 } from 'react-aria-components'; import type { DisclosureGroupProps } from 'react-aria-components'; import type { DisclosurePanelProps } from 'react-aria-components'; diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index e8ebfd654b..9f8d792731 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { CSSProperties } from 'react'; +import type { CSSProperties } from 'react'; /** @public */ export type Breakpoint = 'initial' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; From 7ea79b2ad4aab951a7f291c1018cf4882d85319f Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 17 Apr 2026 10:07:00 +0100 Subject: [PATCH 12/17] ui: add `style` to `CardOwnProps` Signed-off-by: MT Lewis --- packages/ui/report.api.md | 1 + packages/ui/src/components/Card/types.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 04a146ce2c..e7ccca62df 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -793,6 +793,7 @@ export type CardOwnProps = Pick< | 'target' | 'rel' | 'download' + | 'style' >; // @public diff --git a/packages/ui/src/components/Card/types.ts b/packages/ui/src/components/Card/types.ts index c874ec8720..83daf2de08 100644 --- a/packages/ui/src/components/Card/types.ts +++ b/packages/ui/src/components/Card/types.ts @@ -87,6 +87,7 @@ export type CardOwnProps = Pick< | 'target' | 'rel' | 'download' + | 'style' >; /** @public */ From 8bb33cd674105da759b9ba1af81e45cb17f78c1e Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 17 Apr 2026 10:25:56 +0100 Subject: [PATCH 13/17] ui: add affected components to changeset Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: MT Lewis --- .changeset/funny-areas-rescue.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/funny-areas-rescue.md b/.changeset/funny-areas-rescue.md index 1e8a107814..3fafb06e26 100644 --- a/.changeset/funny-areas-rescue.md +++ b/.changeset/funny-areas-rescue.md @@ -2,4 +2,5 @@ '@backstage/ui': minor --- +**Affected components:** Box, Card, Grid, Flex Add support for flex item props (`grow`, `shrink`, and `basis`) to `Box`, `Card`, `Grid`, and `Flex` itself. From 62cc936894e972ef5533c1f3adbb4441c4a96bc0 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 17 Apr 2026 17:46:04 +0100 Subject: [PATCH 14/17] ui: fix organization of .changeset/funny-areas-rescue.md Co-authored-by: Johan Persson Signed-off-by: MT Lewis --- .changeset/funny-areas-rescue.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/funny-areas-rescue.md b/.changeset/funny-areas-rescue.md index 3fafb06e26..0b13201f82 100644 --- a/.changeset/funny-areas-rescue.md +++ b/.changeset/funny-areas-rescue.md @@ -2,5 +2,6 @@ '@backstage/ui': minor --- -**Affected components:** Box, Card, Grid, Flex Add support for flex item props (`grow`, `shrink`, and `basis`) to `Box`, `Card`, `Grid`, and `Flex` itself. + +**Affected components:** Box, Card, Grid, Flex From ea220e9a9d11b1403d569ee6ecff5e9417f70366 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 17 Apr 2026 17:47:29 +0100 Subject: [PATCH 15/17] ui: document FlexItemProps Signed-off-by: MT Lewis --- packages/ui/report.api.md | 5 +---- packages/ui/src/types.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index e7ccca62df..b3cdc5ced4 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -1333,13 +1333,10 @@ export const FlexDefinition: { // @public (undocumented) export type FlexDirection = 'row' | 'column'; -// @public (undocumented) +// @public export interface FlexItemProps { - // (undocumented) basis?: Responsive; - // (undocumented) grow?: Responsive; - // (undocumented) shrink?: Responsive; } diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index 9f8d792731..3a6633bc37 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -122,10 +122,17 @@ export interface PaddingProps { /** @public */ export interface SpaceProps extends MarginProps, PaddingProps {} -/** @public */ +/** + * Flex item properties. + * + * @public + */ export interface FlexItemProps { + /** Controls the flex-grow property. Values of `true` or `false` are converted to `1` or `0` respectively. */ grow?: Responsive; + /** Controls the flex-shrink property. Values of `true` or `false` are converted to `1` or `0` respectively. */ shrink?: Responsive; + /** Controls the flex-basis property. */ basis?: Responsive; } From 68d17251130a26cadcd54684abc0d42600ab1b82 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 17 Apr 2026 17:47:46 +0100 Subject: [PATCH 16/17] ui: style overrides take higher precedence than utility styles in Box Signed-off-by: MT Lewis --- packages/ui/src/components/Box/Box.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Box/Box.tsx b/packages/ui/src/components/Box/Box.tsx index 146292a344..1a2930dd2b 100644 --- a/packages/ui/src/components/Box/Box.tsx +++ b/packages/ui/src/components/Box/Box.tsx @@ -36,7 +36,7 @@ export const Box = forwardRef((props, ref) => { { ref, className: classes.root, - style: { ...ownProps.style, ...utilityStyle }, + style: { ...utilityStyle, ...ownProps.style }, ...dataAttributes, ...restProps, }, From 0912de3a258fb77c46b60af2fa6a43086831f7da Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 17 Apr 2026 17:52:23 +0100 Subject: [PATCH 17/17] ui: transform number to `px` in `basis` utility prop Signed-off-by: MT Lewis --- packages/ui/src/components/Flex/Flex.stories.tsx | 2 +- packages/ui/src/utils/utilityClassMap.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/Flex/Flex.stories.tsx b/packages/ui/src/components/Flex/Flex.stories.tsx index 7812e47770..b392b6e731 100644 --- a/packages/ui/src/components/Flex/Flex.stories.tsx +++ b/packages/ui/src/components/Flex/Flex.stories.tsx @@ -286,7 +286,7 @@ export const FlexItems = meta.story({ }, basis: { control: 'radio', - options: [undefined, '0%', '25%', '50%', '100%', 'auto'], + options: [undefined, '0%', '25%', '50%', '100%', 100, '250px', 'auto'], }, }, render: ({ component: Component, ...args }) => { diff --git a/packages/ui/src/utils/utilityClassMap.ts b/packages/ui/src/utils/utilityClassMap.ts index 376b9fb40c..2b8106eef2 100644 --- a/packages/ui/src/utils/utilityClassMap.ts +++ b/packages/ui/src/utils/utilityClassMap.ts @@ -212,6 +212,7 @@ export const utilityClassMap = { class: 'bui-basis', cssVar: '--basis', values: [], + transform: input => (typeof input === 'number' ? `${input}px` : input), }, } as const satisfies Record< string,