diff --git a/.changeset/actionlist-heading-span-fix.md b/.changeset/actionlist-heading-span-fix.md
new file mode 100644
index 00000000000..0ef051b14a1
--- /dev/null
+++ b/.changeset/actionlist-heading-span-fix.md
@@ -0,0 +1,5 @@
+---
+'@primer/react': patch
+---
+
+Fix invalid HTML nesting in `ActionList.Heading` by applying the visually-hidden styles directly to the heading element instead of wrapping it in a `span`.
diff --git a/packages/react/src/ActionList/Heading.test.tsx b/packages/react/src/ActionList/Heading.test.tsx
index 2e32f776988..2bd6b5f72c5 100644
--- a/packages/react/src/ActionList/Heading.test.tsx
+++ b/packages/react/src/ActionList/Heading.test.tsx
@@ -5,6 +5,7 @@ import {ActionList} from '.'
import {ActionMenu} from '../ActionMenu'
import {implementsClassName, withExpectedConsoleError} from '../utils/testing'
import classes from './Heading.module.css'
+import visuallyHiddenClasses from '../_VisuallyHidden.module.css'
describe('ActionList.Heading', () => {
implementsClassName(
@@ -29,6 +30,41 @@ describe('ActionList.Heading', () => {
expect(heading).toHaveTextContent('Heading')
})
+ it('should not wrap the heading in a span', async () => {
+ const {getByRole} = HTMLRender(
+
+ Heading
+ ,
+ )
+ const heading = getByRole('heading', {level: 1})
+ expect(heading.parentElement?.tagName).not.toBe('SPAN')
+ expect(heading).toHaveClass(classes.ActionListHeader)
+ })
+
+ it('should apply the visually-hidden class to the heading when visuallyHidden is set', async () => {
+ const {getByRole} = HTMLRender(
+
+
+ Heading
+
+ ,
+ )
+ const heading = getByRole('heading', {level: 1})
+ expect(heading).toHaveClass(visuallyHiddenClasses.InternalVisuallyHidden)
+ expect(heading).toHaveClass(classes.ActionListHeader)
+ })
+
+ it('should not apply the visually-hidden class to the heading by default', async () => {
+ const {getByRole} = HTMLRender(
+
+ Heading
+ ,
+ )
+ const heading = getByRole('heading', {level: 1})
+ expect(heading).not.toHaveClass(visuallyHiddenClasses.InternalVisuallyHidden)
+ expect(heading).toHaveClass(classes.ActionListHeader)
+ })
+
it('should label the action list with the heading id', async () => {
const {container, getByRole} = HTMLRender(
diff --git a/packages/react/src/ActionList/Heading.tsx b/packages/react/src/ActionList/Heading.tsx
index 3ffcd843468..b8dd90e305d 100644
--- a/packages/react/src/ActionList/Heading.tsx
+++ b/packages/react/src/ActionList/Heading.tsx
@@ -3,11 +3,11 @@ import {useMergedRefs} from '../hooks'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import {default as HeadingComponent} from '../Heading'
import {ListContext} from './shared'
-import VisuallyHidden from '../_VisuallyHidden'
import {ActionListContainerContext} from './ActionListContainerContext'
import {invariant} from '../utils/invariant'
import {clsx} from 'clsx'
import classes from './Heading.module.css'
+import visuallyHiddenClasses from '../_VisuallyHidden.module.css'
type HeadingLevels = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
type HeadingVariants = 'large' | 'medium' | 'small'
@@ -33,21 +33,23 @@ export const Heading = forwardRef(({as, size, children, visuallyHidden = false,
)
return (
-
-
- {children}
-
-
+
+ {children}
+
)
}) as PolymorphicForwardRefComponent