From 5c7d4b4888c230ebf9595106b479194946bbae2d Mon Sep 17 00:00:00 2001 From: c0mrade Date: Mon, 25 May 2026 23:51:26 +0300 Subject: [PATCH] feat(a11y): dialog semantics + focus trap for the onboarding tour - role=dialog/aria-modal/aria-labelledby/aria-describedby on the onboarding tooltip - trap focus inside the tooltip while the tour runs; Esc skips (lockScroll off so scrollIntoView can still bring each step's target into view) - merge the focus-trap ref with the existing measurement ref via a callback ref - aria-hidden on the click-to-advance target overlay (redundant with the Next button) --- src/components/Onboarding.tsx | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/components/Onboarding.tsx b/src/components/Onboarding.tsx index 63ae244..08a87c1 100644 --- a/src/components/Onboarding.tsx +++ b/src/components/Onboarding.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; +import { useFocusTrap } from '../hooks/useFocusTrap'; interface OnboardingStep { target: string; // data-onboarding attribute value @@ -126,6 +127,17 @@ export default function Onboarding({ steps, onComplete, onSkip }: OnboardingProp onSkip(); }; + // Trap focus inside the tooltip while the tour runs; Esc skips it. + // lockScroll stays off so scrollIntoView can bring each target into view. + const trapRef = useFocusTrap(true, { onEscape: handleSkip, lockScroll: false }); + const setTooltipNode = useCallback( + (node: HTMLDivElement | null) => { + tooltipRef.current = node; + trapRef.current = node; + }, + [trapRef], + ); + // Calculate tooltip position const getTooltipStyle = (): React.CSSProperties => { if (!targetRect) return { opacity: 0 }; @@ -205,7 +217,11 @@ export default function Onboarding({ steps, onComplete, onSkip }: OnboardingProp {/* Tooltip */}
{/* Content */} -

{step.title}

-

{step.description}

+

+ {step.title} +

+

+ {step.description} +

{/* Actions */}
@@ -259,6 +279,7 @@ export default function Onboarding({ steps, onComplete, onSkip }: OnboardingProp {/* Click handler to advance on target click — only when overlay is fully visible */} {targetRect && isVisible && (