diff --git a/src/js/components/plans/header.tsx b/src/js/components/plans/header.tsx index 7a1bf5b80..35edffb5f 100644 --- a/src/js/components/plans/header.tsx +++ b/src/js/components/plans/header.tsx @@ -1,5 +1,5 @@ import PageHeader from '@salesforce/design-system-react/components/page-header'; -import React, { ReactNode } from 'react'; +import React, { ReactNode, useEffect, useRef } from 'react'; import { Trans } from 'react-i18next'; import ProductIcon from '@/js/components/products/icon'; @@ -23,21 +23,38 @@ const Header = ({ preflightStatus?: string | null | undefined; preflightIsValid?: boolean; preflightIsReady?: boolean; -}) => ( - <> - - {{ product: product.title }} {{ version: version.label }} - , - ]} - onRenderActions={onRenderActions ? onRenderActions : null} - icon={} - variant="object-home" - /> - -); +}) => { + const headerRef = useRef(null); + + useEffect(() => { + // Set focus to the header title on mount for proper focus order (WCAG 2.4.3) + // This ensures screen readers land on the heading first, especially on iOS VoiceOver + if (headerRef.current) { + const heading = headerRef.current.querySelector('h1, h2, [class*="heading"]'); + if (heading && heading instanceof HTMLElement) { + // Set tabindex to make heading focusable, then focus it + heading.setAttribute('tabindex', '-1'); + heading.focus(); + } + } + }, [plan.id, product.id, version.id]); + + return ( +
+ + {{ product: product.title }} {{ version: version.label }} + , + ]} + onRenderActions={onRenderActions ? onRenderActions : null} + icon={} + variant="object-home" + /> +
+ ); +}; export default Header; diff --git a/src/js/components/products/header.tsx b/src/js/components/products/header.tsx index 64a5962e9..69c23250d 100644 --- a/src/js/components/products/header.tsx +++ b/src/js/components/products/header.tsx @@ -1,5 +1,5 @@ import PageHeader from '@salesforce/design-system-react/components/page-header'; -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { Trans } from 'react-i18next'; @@ -15,27 +15,43 @@ const Header = ({ versionLabel: string; }) => { const { t } = useTranslation(); + const headerRef = useRef(null); - return ( - - {{ product: product.title }} {{ version: versionLabel }} - , - ] + useEffect(() => { + // Set focus to the header title on mount for proper focus order (WCAG 2.4.3) + // This ensures screen readers land on the heading first, especially on iOS VoiceOver + if (headerRef.current) { + const heading = headerRef.current.querySelector('h1, h2, [class*="heading"]'); + if (heading && heading instanceof HTMLElement) { + // Set tabindex to make heading focusable, then focus it + heading.setAttribute('tabindex', '-1'); + heading.focus(); } - icon={} - variant="object-home" - /> + } + }, [product.id, versionLabel]); + + return ( +
+ + {{ product: product.title }} {{ version: versionLabel }} + , + ] + } + icon={} + variant="object-home" + /> +
); }; diff --git a/src/sass/components/_header.scss b/src/sass/components/_header.scss index a2bbdec28..9827f1833 100644 --- a/src/sass/components/_header.scss +++ b/src/sass/components/_header.scss @@ -12,6 +12,12 @@ border: none; border-radius: 0; box-shadow: none; + + // Ensure programmatically focused headings don't show default focus outline + // Focus is set via JS for accessibility (WCAG 2.4.3) but shouldn't be visually intrusive + [tabindex='-1']:focus { + outline: none; + } } .site-logo {