Keeps a marquee clone's interactive elements clickable while removing them from keyboard tab order and the accessibility tree — solving the "clone cards swallow clicks" bug caused by using `inert`. ## Key Components **`CLONE_FOCUSABLE_SELECTOR`** — Comprehensive CSS selector covering all natively focusable elements: anchors, form controls, `iframe`, `summary`, media with `controls`, `[tabindex]`, and editable content regions. **`useSuppressCloneFocus(active: boolean)`** — React hook that returns a `ref` to attach to a clone wrapper `
`. When `active`, it: - Sets `tabindex="-1"` on every focusable descendant (removes duplicate tab stops) - Watches for DOM mutations that could re-introduce tabbable elements via `MutationObserver` - Leaves click/pointer behavior fully intact (unlike `inert`, which blocks all interaction) - Re-applies suppression only when `tabindex` hasn't already been stamped, preventing infinite observer loops ## Usage Example ```typescript import { useSuppressCloneFocus } from './use-suppress-clone-focus' function MarqueeClone({ isClone }: { isClone: boolean }) { const cloneRef = useSuppressCloneFocus(isClone) return ( // aria-hidden keeps clone out of a11y tree; // hook keeps it out of tab order while staying clickable
Card One Card Two
) } ``` ## Accessibility Contract | Mechanism | Handled by | |---|---| | Out of a11y tree | `aria-hidden` on clone wrapper (caller's responsibility) | | Out of tab order | This hook (`tabindex="-1"` on all focusable descendants) | | Clickable / pointer events | Preserved — clone anchors handle mouse, -click, middle-click, and context menu natively | > **Why not `inert`?** The `inert` attribute also disables pointer events, causing clone cards near the seam of an endless-loop scroll to silently swallow all clicks — the core bug this hook is designed to prevent.