Repository: Sage/carbon (ts)

Issue:
Notice Variant Toast gives Axe error on regions
### Description

Axe report output:

Issue Description: Error: All page content should be contained by landmarks
Ensures all page content is contained by landmarks -https://dequeuniversity.com/rules/axe/4.8/region?application=AxeChrome

Element Location:
.Hlivg

To solve this issue, you need to…:
Some page content is not contained by landmarks

### Reproduction

https://carbon.sage.com/?path=/story/toast--notice

### Steps to reproduce

_No response_

### JIRA ticket numbers (Sage only)

SBS-95481

### Suggested solution

_No response_

### Carbon version

130.0.0

### Design tokens version

_No response_

### 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:
feat(toast): improve component screen reader behaviour
fix #6729

### Proposed behaviour

<!--
A clear and concise description of what changes this PR makes. If applicable, include any UI screenshots to help explain your request. If you are a Sage contributor, please DO NOT share any commercially sensitive information, such as UI screenshots or code from Sage products.
-->

- Set role of "region" to Toast component.
- No axe violation when Toast is rendered. 
- Screen reader announces provided locale string for a visually hidden description of the type of toast. 
- No typo in Select `onKeyDown` prop description. 
- Links in 'Related Components' section in Alert docs go to component's docs page.

### Current behaviour

<!--
A clear and concise description of the behaviour before this change. If applicable, include any UI screenshots to help explain your request. If you are a Sage contributor, please DO NOT share any commercially sensitive information, such as UI screenshots or code from Sage products.
-->

- Toast component has no semantic meaning, this causes an axe violation on render: "All page content should be contained by landmarks".
- Typo in Select `onKeyDown` prop description.
- Links in 'Related Components' section in Alert docs go to default story or are broken. 

### Checklist

<!-- Each PR should include the following -->

- [x] Commits follow our style guide
- [x] 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
- [x] Playwright automation tests added or updated if required
- [x] Storybook added or updated if required
- [x] 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

### Additional context

<!-- Add any other context or links about the pull request here. -->

### Testing instructions

<!--
How can a reviewer test this PR?

If this PR addresses a pre-existing bug, please include a link to a sandbox that reproduces the original bug. A starter template has been provided to help you do this:
<https://stackblitz.com/fork/github/Parsium/carbon-starter>
-->


Patch:
diff --git a/playwright/components/toast/index.ts b/playwright/components/toast/index.ts
index 6d190dc4b..32e67e04f 100644
--- a/playwright/components/toast/index.ts
+++ b/playwright/components/toast/index.ts
@@ -1,6 +1,10 @@
 import type { Page } from "@playwright/test";
+import { TOAST_COMPONENT, TOAST_CONTENT } from "./locators";
 
-// element locators for Toast component
-const toastComponent = (page: Page) => page.locator("[data-component='toast']");
+export const toastComponent = (page: Page) => {
+  return page.locator(TOAST_COMPONENT);
+};
 
-export default toastComponent;
+export const toastContent = (page: Page) => {
+  return page.locator(TOAST_CONTENT);
+};
diff --git a/playwright/components/toast/locators.ts b/playwright/components/toast/locators.ts
new file mode 100644
index 000000000..50586fcf5
--- /dev/null
+++ b/playwright/components/toast/locators.ts
@@ -0,0 +1,3 @@
+// component preview locators
+export const TOAST_COMPONENT = '[data-component="toast"]';
+export const TOAST_CONTENT = '[data-role="toast-content"]';
diff --git a/src/components/alert/alert.mdx b/src/components/alert/alert.mdx
index 81c3f58a4..d8ece0f01 100644
--- a/src/components/alert/alert.mdx
+++ b/src/components/alert/alert.mdx
@@ -22,10 +22,10 @@ import Alert from "carbon-react/lib/components/alert";
 
 ## Related Components
 
-- Longer message which stays on-screen? [Try Message](https://carbon.sage.com/?path=/docs/message--default "Try Message").
-- Longer, time sensitive message that must be dismissed? [Try Toast](https://carbon.sage.com/?path=/docs/toast--default "Try Toast").
-- Confirm or cancel an action I’ve initiated? [Try Confirm](https://carbon.sage.com/?path=/docs/confirm--default-story "Try Confirm").
-- Simple task in context? [Try Dialog](https://carbon.sage.com/?path=/docs/dialog--default-story "Try Dialog").
+- Longer message which stays on-screen? [Try Message](../?path=/docs/message--docs).
+- Longer, time sensitive message that must be dismissed? [Try Toast](../?path=/docs/toast--docs).
+- Confirm or cancel an action initiated? [Try Confirm](../?path=/docs/confirm--docs).
+- Simple task in context? [Try Dialog](../?path=/docs/dialog--docs).
 
 ## Examples
 
diff --git a/src/components/dialog/dialog.pw.tsx b/src/components/dialog/dialog.pw.tsx
index 47bcbbda9..db9c302e6 100644
--- a/src/components/dialog/dialog.pw.tsx
+++ b/src/components/dialog/dialog.pw.tsx
@@ -20,7 +20,7 @@ import {
   Responsive,
   UsingHandle,
 } from "./components.test-pw";
-import toastComponent from "../../../playwright/components/toast";
+import { toastComponent } from "../../../playwright/components/toast";
 import {
   checkAccessibility,
   getStyle,
diff --git a/src/components/select/__internal__/select-textbox/select-textbox.component.tsx b/src/components/select/__internal__/select-textbox/select-textbox.component.tsx
index 9754ea703..0c8696481 100644
--- a/src/components/select/__internal__/select-textbox/select-textbox.component.tsx
+++ b/src/components/select/__internal__/select-textbox/select-textbox.component.tsx
@@ -58,7 +58,7 @@ export interface FormInputPropTypes
   onClick?: (ev: React.MouseEvent<HTMLInputElement>) => void;
   /** Specify a callback triggered on focus */
   onFocus?: (ev: React.FocusEvent<HTMLInputElement>) => void;
-  /** pecify a callback triggered on keuyDown */
+  /** Specify a callback triggered onKeyDown */
   onKeyDown?: (ev: React.KeyboardEvent<HTMLInputElement>) => void;
   /** Placeholder string to be displayed in input */
   placeholder?: string;
diff --git a/src/components/toast/toast.component.tsx b/src/components/toast/toast.component.tsx
index 7c7c57bf0..93dcfa894 100644
--- a/src/components/toast/toast.component.tsx
+++ b/src/components/toast/toast.component.tsx
@@ -16,6 +16,8 @@ import IconButton from "../icon-button";
 import Events from "../../__internal__/utils/helpers/events";
 import useLocale from "../../hooks/__internal__/useLocale";
 import useModalManager from "../../hooks/__internal__/useModalManager";
+import guid from "../../__internal__/utils/helpers/guid";
+import Typography from "../typography";
 
 type ToastVariants =
   | "error"
@@ -46,7 +48,7 @@ export interface ToastProps {
   alignY?: AlignYOptions;
   /** The rendered children of the component. */
   children: React.ReactNode;
-  /** Customizes the appearance in the DLS theme */
+  /** Sets Toast variant */
   variant?: ToastVariants;
   /** Custom className */
   className?: string;
@@ -98,6 +100,8 @@ export const Toast = React.forwardRef<HTMLDivElement, ToastProps>(
     const isNotice = variant === "notice";
     const isNotification = variant === "notification";
     const locale = useLocale();
+    const toastIconId = useRef(guid());
+    const toastContentId = useRef(guid());
 
     const toastRef = useRef<HTMLDivElement | null>(null);
     const timer = useRef<null | ReturnType<typeof setTimeout>>(null);
@@ -166,8 +170,6 @@ export const Toast = React.forwardRef<HTMLDivElement, ToastProps>(
     }, []);
 
     function renderCloseIcon() {
-      if (!onDismiss) return null;
-
       return (
         <IconButton
           aria-label={locale.toast.ariaLabels.close()}
@@ -232,16 +234,20 @@ export const Toast = React.forwardRef<HTMLDivElement, ToastProps>(
                 data-role="toast-type-icon"
               >
                 <Icon type={toastIcon} />
+                <Typography screenReaderOnly id={toastIconId.current}>
+                  {locale.toast[variant]()}
+                </Typography>
               </TypeIcon>
             )}
             <StyledToastContent
               isNotice={isNotice}
               isDismiss={!!onDismiss}
               data-role="toast-content"
+              id={toastContentId.current}
             >
               {children}
             </StyledToastContent>
-            {renderCloseIcon()}
+            {onDismiss && renderCloseIcon()}
           </StyledToast>
         </CSSTransition>
       );
@@ -259,6 +265,10 @@ export const Toast = React.forwardRef<HTMLDivElement, ToastProps>(
           ref={refToPass}
           isNotice={isNotice}
           data-role="toast-wrapper"
+          role="region"
+          aria-labelledby={`${!isNotice && toastIconId.current} ${
+            toastContentId.current
+          }`}
         >
           <TransitionGroup>{renderToastContent()}</TransitionGroup>
         </ToastWrapper>
diff --git a/src/components/toast/toast.mdx b/src/components/toast/toast.mdx
index f01e87add..f7a6103e7 100644
--- a/src/components/toast/toast.mdx
+++ b/src/components/toast/toast.mdx
@@ -27,6 +27,12 @@ Various types are available:
 - **Warning** - Shows information about external factors that impact a task. For example, the user does not have access rights.
 - **Neutral** - Shows information or advice, or provides context.
 
+**Note:** Please use this component sparingly or as a last resort. It is not recommended to use this component as a replacement for form validation. 
+Instead, consider using these altrenatives:
+- Carbon's [built in input validation](../?path=/docs/documentation/validations--docs).
+- Setting focus to a newly-rendered [Message](../?path=/docs/message--docs#with-focus).
+- For error or warning messages that interrupt activity try using [Alert](../?path=/docs/alert--docs).
+
 ## Contents
 
 - [Quick Start](#quick-start)
@@ -42,6 +48,8 @@ To use toast, import the `Toast` and pass the content as children.
 import Toast from "carbon-react/lib/components/toast";
 ```
 
+## Examples
+
 ### Controlled
 
 ```javascript
@@ -145,6 +153,8 @@ To render it at the top of the screen use the `alignY` prop set to `"top"`.
 
 ### Dismissible without autoFocus
 
+**Note:** Please ensure you add an 'aria-live' region to announce the toast when `autoFocus` is disabled, as screen readers will no longer announce the toast when it appears.
+
 <Canvas of={ToastStories.DismissibleWithoutAutoFocus} />
 
 ### Grouped Delayed (stacking)
@@ -178,5 +188,41 @@ The following keys are available to override the translations for this component
       type: "func",
       returnType: "string",
     },
+    {
+      name: "toast.error",
+      description: "The visually hidden message for an error toast",
+      type: "func",
+      returnType: "string",
+    },
+    {
+      name: "toast.info",
+      description: "The visually hidden message for an info toast",
+      type: "func",
+      returnType: "string",
+    },
+    {
+      name: "toast.success",
+      description: "The visually hidden message for a success toast",
+      type: "func",
+      returnType: "string",
+    },
+    {
+      name: "toast.warning",
+      description: "The visually hidden message for a warning toast",
+      type: "func",
+      returnType: "string",
+    },
+    {
+      name: "toast.neutral",
+      description: "The visually hidden message for a neutral toast",
+      type: "func",
+      returnType: "string",
+    },
+    {
+      name: "toast.notification",
+      description: "The visually hidden message for a notification toast",
+      type: "func",
+      returnType: "string",
+    }
   ]}
 />
diff --git a/src/components/toast/toast.pw.tsx b/src/components/toast/toast.pw.tsx
index 3e28a63de..1b66e81be 100644
--- a/src/components/toast/toast.pw.tsx
+++ b/src/components/toast/toast.pw.tsx
@@ -7,7 +7,10 @@ import {
   ToastWhenOtherModalRenders,
   ToastAllAlign,
 } from "./components.test-pw";
-import toastComponent from "../../../playwright/components/toast/index";
+import {
+  toastComponent,
+  toastContent,
+} from "../../../playwright/components/toast/index";
 import { alertDialogPreview } from "../../../playwright/components/dialog";
 import { TOAST_COLORS } from "./toast.config";
 
@@ -42,7 +45,7 @@ test.describe("Toast component", () => {
     }) => {
       await mount(<ToastComponent>{children}</ToastComponent>);
 
-      await expect(toastComponent(page)).toHaveText(children);
+      await expect(toastContent(page)).toHaveText(children);
     });
   });
 
diff --git a/src/locales/de-de.ts b/src/locales/de-de.ts
index 1757d2ef0..53e42ee8c 100644
--- a/src/locales/de-de.ts
+++ b/src/locales/de-de.ts
@@ -214,6 +214,12 @@ const deDE: Partial<Locale> = {
     ariaLabels: {
       close: () => "Schließen",
     },
+    error: () => "",
+    info: () => "",
+    success: () => "",
+    warning: () => "",
+    neutral: () => "",
+    notification: () => "",
   },
   verticalMenuFullScreen: {
     ariaLabels: {
diff --git a/src/locales/en-gb.ts b/src/locales/en-gb.ts
index 2d060cffc..cc5aa18da 100644
--- a/src/locales/en-gb.ts
+++ b/src/locales/en-gb.ts
@@ -220,6 +220,12 @@ const enGB: Locale = {
     ariaLabels: {
       close: () => "Close",
     },
+    error: () => "Error",
+    info: () => "Information",
+    success: () => "Success",
+    warning: () => "Warning",
+    neutral: () => "Information",
+    notification: () => "Notification",
   },
   verticalMenuFullScreen: {
     ariaLabels: {
diff --git a/src/locales/es-es.ts b/src/locales/es-es.ts
index c4df30680..ed5290671 100644
--- a/src/locales/es-es.ts
+++ b/src/locales/es-es.ts
@@ -220,6 +220,12 @@ const esES: Partial<Locale> = {
     ariaLabels: {
       close: () => "Cerrar",
     },
+    error: () => "",
+    info: () => "",
+    success: () => "",
+    warning: () => "",
+    neutral: () => "",
+    notification: () => "",
   },
   verticalMenuFullScreen: {
     ariaLabels: {
diff --git a/src/locales/fr-ca.ts b/src/locales/fr-ca.ts
index ac1b07a9c..ceca347f3 100644
--- a/src/locales/fr-ca.ts
+++ b/src/locales/fr-ca.ts
@@ -222,6 +222,12 @@ const frCA: Partial<Locale> = {
     ariaLabels: {
       close: () => "Fermer",
     },
+    error: () => "",
+    info: () => "",
+    success: () => "",
+    warning: () => "",
+    neutral: () => "",
+    notification: () => "",
   },
   verticalMenuFullScreen: {
     ariaLabels: {
diff --git a/src/locales/fr-fr.ts b/src/locales/fr-fr.ts
index b3c0c0df8..65dcce42f 100644
--- a/src/locales/fr-fr.ts
+++ b/src/locales/fr-fr.ts
@@ -222,6 +222,12 @@ const frFR: Partial<Locale> = {
     ariaLabels: {
       close: () => "Fermer",
     },
+    error: () => "",
+    info: () => "",
+    success: () => "",
+    warning: () => "",
+    neutral: () => "",
+    notification: () => "",
   },
   verticalMenuFullScreen: {
     ariaLabels: {
diff --git a/src/locales/locale.ts b/src/locales/locale.ts
index 7bd4e53f6..7c4c099e8 100644
--- a/src/locales/locale.ts
+++ b/src/locales/locale.ts
@@ -187,6 +187,12 @@ interface Locale {
     ariaLabels: {
       close: () => string;
     };
+    error: () => string;
+    info: () => string;
+    success: () => string;
+    warning: () => string;
+    neutral: () => string;
+    notification: () => string;
   };
   verticalMenuFullScreen: {
     ariaLabels: {


Test patch:
diff --git a/src/components/toast/components.test-pw.tsx b/src/components/toast/components.test-pw.tsx
index 6f02b045a..c7bde0458 100644
--- a/src/components/toast/components.test-pw.tsx
+++ b/src/components/toast/components.test-pw.tsx
@@ -53,46 +53,43 @@ export const ToastWhenOtherModalRenders = ({
   );
 };
 
-export const ToastAllAlign = ({
-  children = "Toast",
-  ...props
-}: Partial<ToastProps>) => {
+export const ToastAllAlign = ({ ...props }: Partial<ToastProps>) => {
   return (
     <>
       <Toast align="left" alignY="top" open {...props}>
-        {children}
+        Toast 1
       </Toast>
 
       <Toast align="center" alignY="top" open {...props}>
-        {children}
+        Toast 2
       </Toast>
 
       <Toast align="right" alignY="top" open {...props}>
-        {children}
+        Toast 3
       </Toast>
 
       <Toast align="left" alignY="center" open {...props}>
-        {children}
+        Toast 4
       </Toast>
 
       <Toast align="center" alignY="center" open {...props}>
-        {children}
+        Toast 5
       </Toast>
 
       <Toast align="right" alignY="center" open {...props}>
-        {children}
+        Toast 6
       </Toast>
 
       <Toast align="left" alignY="bottom" open {...props}>
-        {children}
+        Toast 7
       </Toast>
 
       <Toast align="center" alignY="bottom" open {...props}>
-        {children}
+        Toast 8
       </Toast>
 
       <Toast align="right" alignY="bottom" open {...props}>
-        {children}
+        Toast 9
       </Toast>
     </>
   );