diff --git a/docs/src/app/(docs)/react/components/menu/demos/group-labels/css-modules/index.tsx b/docs/src/app/(docs)/react/components/menu/demos/group-labels/css-modules/index.tsx
index fb1d37e3cdf..d6760e84494 100644
--- a/docs/src/app/(docs)/react/components/menu/demos/group-labels/css-modules/index.tsx
+++ b/docs/src/app/(docs)/react/components/menu/demos/group-labels/css-modules/index.tsx
@@ -21,29 +21,27 @@ export default function ExampleMenu() {
-
+
Sort
-
-
-
-
-
- Date
-
-
-
-
-
- Name
-
-
-
-
-
- Type
-
-
-
+
+
+
+
+ Date
+
+
+
+
+
+ Name
+
+
+
+
+
+ Type
+
+
diff --git a/docs/src/app/(docs)/react/components/menu/demos/group-labels/tailwind/index.tsx b/docs/src/app/(docs)/react/components/menu/demos/group-labels/tailwind/index.tsx
index f5cadcd14c5..ce87e94e0d8 100644
--- a/docs/src/app/(docs)/react/components/menu/demos/group-labels/tailwind/index.tsx
+++ b/docs/src/app/(docs)/react/components/menu/demos/group-labels/tailwind/index.tsx
@@ -20,40 +20,38 @@ export default function ExampleMenu() {
-
+
Sort
-
-
-
-
-
- Date
-
-
-
-
-
- Name
-
-
-
-
-
- Type
-
-
-
+
+
+
+
+ Date
+
+
+
+
+
+ Name
+
+
+
+
+
+ Type
+
+
diff --git a/docs/src/app/(docs)/react/components/menu/page.mdx b/docs/src/app/(docs)/react/components/menu/page.mdx
index 3b4c45945b0..84d43daab23 100644
--- a/docs/src/app/(docs)/react/components/menu/page.mdx
+++ b/docs/src/app/(docs)/react/components/menu/page.mdx
@@ -93,7 +93,7 @@ Use the `closeOnClick` prop to change whether the menu closes when an item is cl
### Group labels
-Use the `` part to add a label to a ``
+Use the `` part to add a label to a `` or ``.
import { DemoMenuGroupLabels } from './demos/group-labels';
diff --git a/docs/src/error-codes.json b/docs/src/error-codes.json
index f879c383ecf..ab3c38e3e10 100644
--- a/docs/src/error-codes.json
+++ b/docs/src/error-codes.json
@@ -28,7 +28,7 @@
"28": "Base UI: FieldRootContext is missing. Field parts must be placed within .",
"29": "[Floating UI]: Invalid grid - item width at index %s is greater than grid columns",
"30": "Base UI: MenuCheckboxItemContext is missing. MenuCheckboxItem parts must be placed within .",
- "31": "Base UI: MenuGroupRootContext is missing. Menu group parts must be used within .",
+ "31": "Base UI: MenuGroupRootContext is missing. Menu group parts must be used within or .",
"32": "Base UI: is missing.",
"33": "Base UI: MenuPositionerContext is missing. MenuPositioner parts must be placed within .",
"34": "Base UI: MenuRadioGroupContext is missing. MenuRadioGroup parts must be placed within .",
diff --git a/packages/react/src/menu/group-label/MenuGroupLabel.test.tsx b/packages/react/src/menu/group-label/MenuGroupLabel.test.tsx
index 43f5503df06..52fcdc1c9b8 100644
--- a/packages/react/src/menu/group-label/MenuGroupLabel.test.tsx
+++ b/packages/react/src/menu/group-label/MenuGroupLabel.test.tsx
@@ -79,5 +79,45 @@ describe('', () => {
const group = screen.getByRole('group');
expect(group).toHaveAttribute('aria-labelledby', 'test-group');
});
+
+ it("should reference the generated id in RadioGroup's `aria-labelledby`", async () => {
+ await render(
+
+
+
+
+
+ Test group
+
+
+
+
+ ,
+ );
+
+ const radioGroup = screen.getByRole('group');
+ const groupLabel = screen.getByText('Test group');
+
+ expect(radioGroup).toHaveAttribute('aria-labelledby', groupLabel.id);
+ });
+
+ it("should reference the provided id in RadioGroup's `aria-labelledby`", async () => {
+ await render(
+
+
+
+
+
+ Test group
+
+
+
+
+ ,
+ );
+
+ const radioGroup = screen.getByRole('group');
+ expect(radioGroup).toHaveAttribute('aria-labelledby', 'test-group');
+ });
});
});
diff --git a/packages/react/src/menu/group/MenuGroupContext.ts b/packages/react/src/menu/group/MenuGroupContext.ts
index 6331afd21e9..0b88f15e73c 100644
--- a/packages/react/src/menu/group/MenuGroupContext.ts
+++ b/packages/react/src/menu/group/MenuGroupContext.ts
@@ -11,7 +11,7 @@ export function useMenuGroupRootContext() {
const context = React.useContext(MenuGroupContext);
if (context === undefined) {
throw new Error(
- 'Base UI: MenuGroupRootContext is missing. Menu group parts must be used within .',
+ 'Base UI: MenuGroupRootContext is missing. Menu group parts must be used within or .',
);
}
diff --git a/packages/react/src/menu/radio-group/MenuRadioGroup.tsx b/packages/react/src/menu/radio-group/MenuRadioGroup.tsx
index 55bd79eee6f..789ba5b3342 100644
--- a/packages/react/src/menu/radio-group/MenuRadioGroup.tsx
+++ b/packages/react/src/menu/radio-group/MenuRadioGroup.tsx
@@ -3,6 +3,7 @@ import * as React from 'react';
import { useControlled } from '@base-ui/utils/useControlled';
import { useStableCallback } from '@base-ui/utils/useStableCallback';
import { MenuRadioGroupContext } from './MenuRadioGroupContext';
+import { MenuGroupContext } from '../group/MenuGroupContext';
import { useRenderElement } from '../../internals/useRenderElement';
import type { BaseUIComponentProps } from '../../internals/types';
import type { MenuRoot } from '../root/MenuRoot';
@@ -29,6 +30,8 @@ export const MenuRadioGroup = React.memo(
...elementProps
} = componentProps;
+ const [labelId, setLabelId] = React.useState(undefined);
+
const [value, setValueUnwrapped] = useControlled({
controlled: valueProp,
default: defaultValue,
@@ -54,6 +57,7 @@ export const MenuRadioGroup = React.memo(
ref: forwardedRef,
props: {
role: 'group',
+ 'aria-labelledby': labelId,
'aria-disabled': disabled || undefined,
...elementProps,
},
@@ -68,8 +72,12 @@ export const MenuRadioGroup = React.memo(
[value, setValue, disabled],
);
+ const groupContext = React.useMemo(() => ({ setLabelId }), [setLabelId]);
+
return (
- {element}
+
+ {element}
+
);
}),
);