From adcdd2f2e09b19ac41aec2d1d90a82aad7be4f14 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 23 Mar 2026 08:49:24 +0000 Subject: [PATCH 1/6] Improve ring on Menu Signed-off-by: Charles de Dreuille --- .changeset/ui-menu-item-style-refactor.md | 7 ++ .../ui/src/components/Menu/Menu.module.css | 74 +++++++++---------- packages/ui/src/components/Menu/Menu.tsx | 14 ++-- 3 files changed, 48 insertions(+), 47 deletions(-) create mode 100644 .changeset/ui-menu-item-style-refactor.md diff --git a/.changeset/ui-menu-item-style-refactor.md b/.changeset/ui-menu-item-style-refactor.md new file mode 100644 index 0000000000..7ff7b28c9e --- /dev/null +++ b/.changeset/ui-menu-item-style-refactor.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Simplified the `Menu` component's item structure by removing the inner wrapper element and applying styles directly to the menu item, improving DOM clarity. + +**Affected components:** Menu diff --git a/packages/ui/src/components/Menu/Menu.module.css b/packages/ui/src/components/Menu/Menu.module.css index 9acedd106e..f0b21eda3a 100644 --- a/packages/ui/src/components/Menu/Menu.module.css +++ b/packages/ui/src/components/Menu/Menu.module.css @@ -72,48 +72,10 @@ box-sizing: border-box; outline: none; padding-block: var(--bui-space-1); + padding-inline: var(--bui-space-1); } .bui-MenuItem { - padding-inline: var(--bui-space-1); - display: block; - - &[data-focus-visible] { - outline: none; - } - - &[data-focus-visible] { - outline: 2px solid var(--bui-ring); - outline-offset: -2px; - } - - &[data-focused] .bui-MenuItemWrapper { - background: var(--bui-bg-neutral-2); - color: var(--bui-fg-primary); - } - - &[data-open] .bui-MenuItemWrapper { - background: var(--bui-bg-neutral-2); - color: var(--bui-fg-primary); - } - - &[data-color='danger'] .bui-MenuItemWrapper { - color: var(--bui-fg-danger); - } - - &[data-color='danger'][data-focused] .bui-MenuItemWrapper { - background: var(--bui-bg-danger); - color: var(--bui-fg-danger); - } - - &[data-has-submenu] { - & > .bui-MenuItemWrapper > .bui-MenuItemArrow { - display: block; - } - } - } - - .bui-MenuItemWrapper { display: flex; align-items: center; justify-content: space-between; @@ -125,6 +87,40 @@ color: var(--bui-fg-primary); font-size: var(--bui-font-size-3); gap: var(--bui-space-6); + + &[data-focus-visible] { + outline: none; + } + + &[data-focus-visible] { + outline: 2px solid var(--bui-ring); + outline-offset: -2px; + } + + &[data-focused] { + background: var(--bui-bg-neutral-2); + color: var(--bui-fg-primary); + } + + &[data-open] { + background: var(--bui-bg-neutral-2); + color: var(--bui-fg-primary); + } + + &[data-color='danger'] { + color: var(--bui-fg-danger); + } + + &[data-color='danger'][data-focused] { + background: var(--bui-bg-danger); + color: var(--bui-fg-danger); + } + + &[data-has-submenu] { + & > .bui-MenuItemArrow { + display: block; + } + } } .bui-MenuItemListBox { diff --git a/packages/ui/src/components/Menu/Menu.tsx b/packages/ui/src/components/Menu/Menu.tsx index cb6028c45a..543b4cfdf7 100644 --- a/packages/ui/src/components/Menu/Menu.tsx +++ b/packages/ui/src/components/Menu/Menu.tsx @@ -357,14 +357,12 @@ export const MenuItem = (props: MenuItemProps) => { handleAction(); }} > -
-
- {iconStart} - {children} -
-
- -
+
+ {iconStart} + {children} +
+
+
); From 87e696a0616246413cd6a621ff54694f5e2bc114 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 23 Mar 2026 08:56:47 +0000 Subject: [PATCH 2/6] Update Menu.module.css Signed-off-by: Charles de Dreuille --- packages/ui/src/components/Menu/Menu.module.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/ui/src/components/Menu/Menu.module.css b/packages/ui/src/components/Menu/Menu.module.css index f0b21eda3a..0fa530359a 100644 --- a/packages/ui/src/components/Menu/Menu.module.css +++ b/packages/ui/src/components/Menu/Menu.module.css @@ -88,10 +88,6 @@ font-size: var(--bui-font-size-3); gap: var(--bui-space-6); - &[data-focus-visible] { - outline: none; - } - &[data-focus-visible] { outline: 2px solid var(--bui-ring); outline-offset: -2px; From 3465bf22fcbc34420032e603671867e878146be7 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 23 Mar 2026 10:27:37 +0000 Subject: [PATCH 3/6] Remove wrapper Signed-off-by: Charles de Dreuille --- packages/ui/src/components/Menu/Menu.tsx | 29 +++++++++---------- packages/ui/src/components/Menu/definition.ts | 2 -- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/packages/ui/src/components/Menu/Menu.tsx b/packages/ui/src/components/Menu/Menu.tsx index 543b4cfdf7..e8de1f6282 100644 --- a/packages/ui/src/components/Menu/Menu.tsx +++ b/packages/ui/src/components/Menu/Menu.tsx @@ -66,6 +66,7 @@ import { isInternalLink } from '../../utils/linkUtils'; import { getNodeText } from '../../analytics/getNodeText'; import { Box } from '../Box'; import { BgReset } from '../../hooks/useBg'; +import { useCallback } from 'react'; // The height will be used for virtualized menus. It should match the size set in CSS for each menu item. const rowHeight = 32; @@ -308,7 +309,7 @@ export const MenuItem = (props: MenuItemProps) => { ); const { classes, iconStart, children, href } = ownProps; - const handleAction = () => { + const handleAction = useCallback(() => { if (href) { const text = restProps['aria-label'] ?? getNodeText(children) ?? String(href); @@ -316,7 +317,7 @@ export const MenuItem = (props: MenuItemProps) => { attributes: { to: String(href) }, }); } - }; + }, [href, restProps, analytics, children]); // External links open in new tab via window.open instead of client-side routing if (href && !isInternalLink(href)) { @@ -332,14 +333,12 @@ export const MenuItem = (props: MenuItemProps) => { window.open(href, '_blank', 'noopener,noreferrer'); }} > -
-
- {iconStart} - {children} -
-
- -
+
+ {iconStart} + {children} +
+
+
); @@ -382,13 +381,11 @@ export const MenuListBoxItem = (props: MenuListBoxItemProps) => { className={classes.root} {...restProps} > -
-
-
- -
- {children} +
+
+
+ {children}
); diff --git a/packages/ui/src/components/Menu/definition.ts b/packages/ui/src/components/Menu/definition.ts index 076e9ce3ae..63c46a24a3 100644 --- a/packages/ui/src/components/Menu/definition.ts +++ b/packages/ui/src/components/Menu/definition.ts @@ -100,7 +100,6 @@ export const MenuItemDefinition = defineComponent()({ styles, classNames: { root: 'bui-MenuItem', - itemWrapper: 'bui-MenuItemWrapper', itemContent: 'bui-MenuItemContent', itemArrow: 'bui-MenuItemArrow', }, @@ -121,7 +120,6 @@ export const MenuListBoxItemDefinition = styles, classNames: { root: 'bui-MenuItemListBox', - itemWrapper: 'bui-MenuItemWrapper', itemContent: 'bui-MenuItemContent', check: 'bui-MenuItemListBoxCheck', }, From 85e2327f5f32e255ed4a6c8fcc91006c94abab51 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 23 Mar 2026 10:33:58 +0000 Subject: [PATCH 4/6] Cleaning Signed-off-by: Charles de Dreuille --- .../ui/src/components/Menu/Menu.module.css | 19 ++++++++++++++++--- packages/ui/src/components/Menu/Menu.tsx | 9 ++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/Menu/Menu.module.css b/packages/ui/src/components/Menu/Menu.module.css index 0fa530359a..31aae5a990 100644 --- a/packages/ui/src/components/Menu/Menu.module.css +++ b/packages/ui/src/components/Menu/Menu.module.css @@ -120,10 +120,23 @@ } .bui-MenuItemListBox { - padding-inline: var(--bui-space-1); - display: block; + display: flex; + align-items: center; + height: 2rem; + padding-inline: var(--bui-space-2); + border-radius: var(--bui-radius-2); + outline: none; + cursor: default; + color: var(--bui-fg-primary); + font-size: var(--bui-font-size-3); - &:hover .bui-MenuItemWrapper { + &[data-focus-visible] { + outline: 2px solid var(--bui-ring); + outline-offset: -2px; + } + + &[data-hovered], + &[data-focused] { background: var(--bui-bg-neutral-2); color: var(--bui-fg-primary); } diff --git a/packages/ui/src/components/Menu/Menu.tsx b/packages/ui/src/components/Menu/Menu.tsx index e8de1f6282..2c411dd4c9 100644 --- a/packages/ui/src/components/Menu/Menu.tsx +++ b/packages/ui/src/components/Menu/Menu.tsx @@ -66,7 +66,6 @@ import { isInternalLink } from '../../utils/linkUtils'; import { getNodeText } from '../../analytics/getNodeText'; import { Box } from '../Box'; import { BgReset } from '../../hooks/useBg'; -import { useCallback } from 'react'; // The height will be used for virtualized menus. It should match the size set in CSS for each menu item. const rowHeight = 32; @@ -309,7 +308,7 @@ export const MenuItem = (props: MenuItemProps) => { ); const { classes, iconStart, children, href } = ownProps; - const handleAction = useCallback(() => { + const captureHrefClick = () => { if (href) { const text = restProps['aria-label'] ?? getNodeText(children) ?? String(href); @@ -317,7 +316,7 @@ export const MenuItem = (props: MenuItemProps) => { attributes: { to: String(href) }, }); } - }, [href, restProps, analytics, children]); + }; // External links open in new tab via window.open instead of client-side routing if (href && !isInternalLink(href)) { @@ -329,7 +328,7 @@ export const MenuItem = (props: MenuItemProps) => { {...restProps} onAction={() => { restProps.onAction?.(); - handleAction(); + captureHrefClick(); window.open(href, '_blank', 'noopener,noreferrer'); }} > @@ -353,7 +352,7 @@ export const MenuItem = (props: MenuItemProps) => { {...restProps} onAction={() => { restProps.onAction?.(); - handleAction(); + captureHrefClick(); }} >
From 5df9bce512e25f1dc363445d00913edbc5bbeb73 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 23 Mar 2026 11:16:15 +0000 Subject: [PATCH 5/6] Improve link items Signed-off-by: Charles de Dreuille --- .../ui/src/components/Menu/Menu.module.css | 2 + packages/ui/src/components/Menu/Menu.tsx | 45 +++++-------------- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/packages/ui/src/components/Menu/Menu.module.css b/packages/ui/src/components/Menu/Menu.module.css index 31aae5a990..d6843103a9 100644 --- a/packages/ui/src/components/Menu/Menu.module.css +++ b/packages/ui/src/components/Menu/Menu.module.css @@ -87,6 +87,7 @@ color: var(--bui-fg-primary); font-size: var(--bui-font-size-3); gap: var(--bui-space-6); + cursor: pointer; &[data-focus-visible] { outline: 2px solid var(--bui-ring); @@ -129,6 +130,7 @@ cursor: default; color: var(--bui-fg-primary); font-size: var(--bui-font-size-3); + cursor: pointer; &[data-focus-visible] { outline: 2px solid var(--bui-ring); diff --git a/packages/ui/src/components/Menu/Menu.tsx b/packages/ui/src/components/Menu/Menu.tsx index 2c411dd4c9..8008a071bb 100644 --- a/packages/ui/src/components/Menu/Menu.tsx +++ b/packages/ui/src/components/Menu/Menu.tsx @@ -308,51 +308,26 @@ export const MenuItem = (props: MenuItemProps) => { ); const { classes, iconStart, children, href } = ownProps; - const captureHrefClick = () => { - if (href) { - const text = - restProps['aria-label'] ?? getNodeText(children) ?? String(href); - analytics.captureEvent('click', text, { - attributes: { to: String(href) }, - }); - } - }; - - // External links open in new tab via window.open instead of client-side routing - if (href && !isInternalLink(href)) { - return ( - { - restProps.onAction?.(); - captureHrefClick(); - window.open(href, '_blank', 'noopener,noreferrer'); - }} - > -
- {iconStart} - {children} -
-
- -
-
- ); - } + const isExternal = href && !isInternalLink(href); return ( { restProps.onAction?.(); - captureHrefClick(); + if (href) { + const text = + restProps['aria-label'] ?? getNodeText(children) ?? String(href); + analytics.captureEvent('click', text, { + attributes: { to: String(href) }, + }); + } }} >
From 96e510b88a237ee7e9115dcacf46b3542501f157 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 23 Mar 2026 11:33:00 +0000 Subject: [PATCH 6/6] Update Menu.module.css Signed-off-by: Charles de Dreuille --- packages/ui/src/components/Menu/Menu.module.css | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/ui/src/components/Menu/Menu.module.css b/packages/ui/src/components/Menu/Menu.module.css index d6843103a9..02446eea5b 100644 --- a/packages/ui/src/components/Menu/Menu.module.css +++ b/packages/ui/src/components/Menu/Menu.module.css @@ -69,10 +69,8 @@ box-sizing: border-box; overflow: auto; min-width: 150px; - box-sizing: border-box; outline: none; padding-block: var(--bui-space-1); - padding-inline: var(--bui-space-1); } .bui-MenuItem { @@ -83,11 +81,11 @@ padding-inline: var(--bui-space-2); border-radius: var(--bui-radius-2); outline: none; - cursor: default; color: var(--bui-fg-primary); font-size: var(--bui-font-size-3); gap: var(--bui-space-6); cursor: pointer; + margin-inline: var(--bui-space-1); &[data-focus-visible] { outline: 2px solid var(--bui-ring); @@ -127,7 +125,6 @@ padding-inline: var(--bui-space-2); border-radius: var(--bui-radius-2); outline: none; - cursor: default; color: var(--bui-fg-primary); font-size: var(--bui-font-size-3); cursor: pointer;