Repository: Sage/carbon (ts)

Issue:
Accordion - accessibility issue
### Description

I think accessibility is broken with the 147.0.0.
For accordions, you made this change.

![Image](https://github.com/user-attachments/assets/66b557bb-b0a1-4e54-8e28-f3e709a6eac4)

Problem is StyledAccordionTitle generates a h3.
If we have a Heading=h1 but no h2, we have an accessibility issue.
When i try to pass a <Typography variant="h3" as="h2"> in the title of the accordion, it still fails because it generates `<h3><h2>my title</h2></h3>`

### Reproduction

https://stackblitz.com/edit/parsium-carbon-starter-xe71meob?file=src%2FApp.tsx

### Steps to reproduce

_No response_

### JIRA ticket numbers (Sage only)

_No response_

### Suggested solution

Revert the code so that it generates a H3 if title is a string but uses the title's ReactNode otherwise.

### Carbon version

147.0.0

### Design tokens version

4.35.0

### Relevant browsers

Chrome

### Relevant OSs

MacOS

### Additional context

_No response_

### Confidentiality

- [x] I confirm there is no confidential or commercially sensitive information included.

Pull request:
fix(accordion): ensure custom title is not wrapped in a heading tag when provided
fix #7202

### Proposed behaviour

Ensure a custom heading provided via the title prop is not wrapped in a `<h3>` tag.

### Current behaviour

When a consumer passes a custom header as a React node via Accordion's title prop, the node is wrapped within a `<h3>` tag. This causes the Axe violation "Heading levels should only increase by one" if a consumer doesn't have a `<h2>` defined earlier in the document.

### Checklist

- [ ] Commits follow our style guide
- [ ] Related issues linked in commit messages if required
- [ ] Screenshots are included in the PR if useful
- [ ] All themes are supported if required
- [ ] Unit tests added or updated if required
- [ ] Playwright automation tests added or updated if required
- [ ] Storybook added or updated if required
- [ ] Translations added or updated (including creating or amending translation keys table in storybook) if required
- [ ] Typescript `d.ts` file added or updated if required
- [ ] Related docs have been updated if required

#### QA

- [ ] Tested in provided StackBlitz sandbox/Storybook
- [ ] Add new Playwright test coverage if required
- [ ] Carbon implementation matches Design System/designs
- [ ] UI Tests GitHub check reviewed if required


Patch:
diff --git a/src/components/accordion/accordion.component.tsx b/src/components/accordion/accordion.component.tsx
index e71aa9bfd..b20d0cc23 100644
--- a/src/components/accordion/accordion.component.tsx
+++ b/src/components/accordion/accordion.component.tsx
@@ -185,13 +185,17 @@ export const Accordion = React.forwardRef<
             data-element="accordion-headings-container"
             hasValidationIcon={showValidationIcon}
           >
-            <StyledAccordionTitle
-              data-element="accordion-title"
-              size={size}
-              variant={variant}
-            >
-              {getTitle()}
-            </StyledAccordionTitle>
+            {typeof title === "string" ? (
+              <StyledAccordionTitle
+                data-element="accordion-title"
+                size={size}
+                variant={variant}
+              >
+                {isExpanded ? openTitle || title : title}
+              </StyledAccordionTitle>
+            ) : (
+              getTitle()
+            )}
 
             {variant !== "subtle" && (
               <>
diff --git a/src/components/accordion/accordion.mdx b/src/components/accordion/accordion.mdx
index 5f3572b62..0fd99024a 100644
--- a/src/components/accordion/accordion.mdx
+++ b/src/components/accordion/accordion.mdx
@@ -78,6 +78,16 @@ The default version of `Accordion` has a white background and no side borders.
 
 <Canvas of={AccordionStories.Default} />
 
+### With title
+
+The `Accordion` can be rendered with a title by setting the `title` prop.
+
+<Canvas of={AccordionStories.WithTitle} />
+
+The `title` prop also supports passing a React node for more complex titles.
+
+<Canvas of={AccordionStories.WithComplexTitle} />
+
 ### With disabled content padding
 
 The internal padding of the `Accordion` can be removed by setting the `disableContentPadding` prop to `true`.
diff --git a/src/components/accordion/accordion.pw.tsx b/src/components/accordion/accordion.pw.tsx
index e091f4508..5a9569a03 100644
--- a/src/components/accordion/accordion.pw.tsx
+++ b/src/components/accordion/accordion.pw.tsx
@@ -38,6 +38,7 @@ import {
 } from "./components.test-pw";
 
 import { additionalButton as splitAdditionalButtons } from "../../../playwright/components/split-button";
+import Typography from "../typography";
 
 const testData = [CHARACTERS.DIACRITICS, CHARACTERS.SPECIALCHARACTERS];
 
@@ -637,4 +638,30 @@ test.describe("Accessibility tests for Accordion", () => {
 
     await checkAccessibility(page);
   });
+
+  test("should pass accessibility tests for Accordion when title is a string", async ({
+    mount,
+    page,
+  }) => {
+    await mount(<AccordionDefault title="title" />);
+
+    await checkAccessibility(page);
+  });
+
+  test("should pass accessibility tests for Accordion when title is a React node", async ({
+    mount,
+    page,
+  }) => {
+    await mount(
+      <AccordionDefault
+        title={
+          <Typography variant="h4" backgroundColor="blue" color="yellow">
+            Title
+          </Typography>
+        }
+      />,
+    );
+
+    await checkAccessibility(page);
+  });
 });
diff --git a/src/components/accordion/accordion.stories.tsx b/src/components/accordion/accordion.stories.tsx
index 6d0452548..a21e8ca97 100644
--- a/src/components/accordion/accordion.stories.tsx
+++ b/src/components/accordion/accordion.stories.tsx
@@ -35,6 +35,34 @@ export const Default: Story = () => {
 };
 Default.storyName = "Default";
 
+export const WithTitle: Story = () => {
+  return (
+    <Accordion title="Title">
+      <Box mt={2}>Content</Box>
+      <Box>Content</Box>
+      <Box>Content</Box>
+    </Accordion>
+  );
+};
+WithTitle.storyName = "With Title";
+
+export const WithComplexTitle: Story = () => {
+  return (
+    <Accordion
+      title={
+        <Typography variant="h4" backgroundColor="blue" color="yellow">
+          Title
+        </Typography>
+      }
+    >
+      <Box mt={2}>Content</Box>
+      <Box>Content</Box>
+      <Box>Content</Box>
+    </Accordion>
+  );
+};
+WithComplexTitle.storyName = "With Complex Title";
+
 export const WithDisableContentPadding: Story = () => {
   return (
     <Accordion disableContentPadding title="Heading">


Test patch:
diff --git a/src/components/accordion/accordion.test.tsx b/src/components/accordion/accordion.test.tsx
index e3c9219f6..6a7e4dd5f 100644
--- a/src/components/accordion/accordion.test.tsx
+++ b/src/components/accordion/accordion.test.tsx
@@ -190,6 +190,20 @@ describe("Accordion", () => {
     expect(screen.getByRole("button")).toHaveTextContent("Less info");
   });
 
+  it("should display the `openTitle` when open and the `openTitle` and `title` props are provided", () => {
+    render(
+      <Accordion title={<h4>Title in H4</h4>} expanded openTitle="Less info" />,
+    );
+
+    expect(screen.getByRole("button")).toHaveTextContent("Less info");
+  });
+
+  it("should display the `title` when open and `title` prop is provided as a React node", () => {
+    render(<Accordion title={<h4>Title in H4</h4>} expanded />);
+
+    expect(screen.getByRole("button")).toHaveTextContent("Title in H4");
+  });
+
   it("should display the `title` when open if the `openTitle` prop is not provided", () => {
     render(<Accordion title="Title" expanded />);