[{"data":1,"prerenderedAt":2370},["ShallowReactive",2],{"site-header-nav":3,"page-\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002F":156,"content-navigation":2296},[4,66,70],{"title":5,"path":6,"stem":7,"children":8},"Core Accessibility Principles For Modern Frameworks","\u002Fcore-accessibility-principles-for-modern-frameworks","core-accessibility-principles-for-modern-frameworks",[9,12,18,24,36,48,60],{"title":10,"path":6,"stem":11},"Core Accessibility Principles for Modern Frameworks","core-accessibility-principles-for-modern-frameworks\u002Findex",{"title":13,"path":14,"stem":15,"children":16},"Accessible Color Contrast & Theming","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Faccessible-color-contrast-theming","core-accessibility-principles-for-modern-frameworks\u002Faccessible-color-contrast-theming\u002Findex",[17],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":22},"Accessible Form Validation & Error States in Modern Frameworks","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Faccessible-form-validation-error-states","core-accessibility-principles-for-modern-frameworks\u002Faccessible-form-validation-error-states\u002Findex",[23],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":28},"Focus Management Strategies for SPAs","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas","core-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002Findex",[29,30],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":34},"Handling Focus Restoration After Dynamic Route Changes","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002Fhandling-focus-restoration-after-dynamic-route-changes","core-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002Fhandling-focus-restoration-after-dynamic-route-changes\u002Findex",[35],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":40},"Keyboard Navigation Patterns for Modals","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fkeyboard-navigation-patterns-for-modals","core-accessibility-principles-for-modern-frameworks\u002Fkeyboard-navigation-patterns-for-modals\u002Findex",[41,42],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":46},"Building Accessible Dropdowns Without External UI Kits","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fkeyboard-navigation-patterns-for-modals\u002Fbuilding-accessible-dropdowns-without-external-ui-kits","core-accessibility-principles-for-modern-frameworks\u002Fkeyboard-navigation-patterns-for-modals\u002Fbuilding-accessible-dropdowns-without-external-ui-kits\u002Findex",[47],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":52},"Screen Reader Compatibility Testing for Modern Frameworks","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing","core-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing\u002Findex",[53,54],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"Testing ARIA Live Regions with Jest and Testing Library","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing\u002Ftesting-aria-live-regions-with-jest-and-testing-library","core-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing\u002Ftesting-aria-live-regions-with-jest-and-testing-library\u002Findex",[59],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"Semantic HTML vs ARIA in Component Trees","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fsemantic-html-vs-aria-in-component-trees","core-accessibility-principles-for-modern-frameworks\u002Fsemantic-html-vs-aria-in-component-trees\u002Findex",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69},"Modern Framework Accessibility","\u002F","index",{"title":71,"path":72,"stem":73,"children":74},"React Nextjs Accessibility Patterns","\u002Freact-nextjs-accessibility-patterns","react-nextjs-accessibility-patterns",[75,78,90,102,108,126,144],{"title":76,"path":72,"stem":77},"React & Next.js Accessibility Patterns","react-nextjs-accessibility-patterns\u002Findex",{"title":79,"path":80,"stem":81,"children":82},"Accessible Component Libraries in React","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react","react-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002Findex",[83,84],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":88},"Building Accessible Tabs in React Without Radix UI","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002Fbuilding-accessible-tabs-in-react-without-radix-ui","react-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002Fbuilding-accessible-tabs-in-react-without-radix-ui\u002Findex",[89],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":94},"Dynamic Content & State Announcements in React & Next.js","\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements","react-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Findex",[95,96],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":100},"Implementing React Context for Global Accessibility Preferences","\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Freact-context-for-global-accessibility-preferences","react-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Freact-context-for-global-accessibility-preferences\u002Findex",[101],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":106},"Form Handling with React Hook Form & Accessibility","\u002Freact-nextjs-accessibility-patterns\u002Fform-handling-with-react-hook-form-a11y","react-nextjs-accessibility-patterns\u002Fform-handling-with-react-hook-form-a11y\u002Findex",[107],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":112},"Next.js App Router & A11y: Implementation Guide for Modern Frameworks","\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y","react-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Findex",[113,114,120],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":118},"Implementing Skip Links in Next.js App Router: A Step-by-Step Guide","\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fimplementing-skip-links-in-nextjs-app-router","react-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fimplementing-skip-links-in-nextjs-app-router\u002Findex",[119],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":124},"Next.js Dynamic Imports and Keyboard Navigation: A Complete A11y Implementation Guide","\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fnextjs-dynamic-imports-and-keyboard-navigation","react-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fnextjs-dynamic-imports-and-keyboard-navigation\u002Findex",[125],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":130},"React Hooks for Accessibility: Implementation Patterns & State Management","\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility","react-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Findex",[131,132,138],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":136},"Fixing Focus Trap Issues in React Portals","\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Ffixing-focus-trap-issues-in-react-portals","react-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Ffixing-focus-trap-issues-in-react-portals\u002Findex",[137],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":142},"Making React useEffect Accessible for Screen Readers","\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Fmaking-react-useeffect-accessible-for-screen-readers","react-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Fmaking-react-useeffect-accessible-for-screen-readers\u002Findex",[143],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":148},"Server Components & Client-Side Interactivity","\u002Freact-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity","react-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity\u002Findex",[149,150],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":154},"Handling Accessible Modals in Next.js 14 Server Components","\u002Freact-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity\u002Fhandling-accessible-modals-in-nextjs-14-server-components","react-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity\u002Fhandling-accessible-modals-in-nextjs-14-server-components\u002Findex",[155],{"title":151,"path":152,"stem":153},{"id":157,"title":127,"body":158,"date":2289,"description":2290,"extension":2291,"image":2289,"meta":2292,"modifiedAt":2289,"navigation":327,"noindex":2293,"path":128,"publishedAt":2289,"seo":2294,"stem":129,"updatedAt":2289,"__hash__":2295},"content\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Findex.md",{"type":159,"value":160,"toc":2271},"minimark",[161,165,174,180,209,214,228,231,236,243,253,259,261,268,280,283,290,1061,1066,1068,1072,1095,1101,1755,1764,1766,1770,1778,1789,1795,2134,2143,2145,2149,2219,2221,2225,2242,2252,2261,2267],[162,163,127],"h1",{"id":164},"react-hooks-for-accessibility-implementation-patterns-state-management",[166,167,168,169,173],"p",{},"Custom React hooks bridge the gap between declarative UI state and WCAG compliance. By encapsulating focus management, live region announcements, and keyboard navigation into reusable logic, developers can maintain accessible patterns without polluting component trees. This guide explores how to implement ",[170,171,76],"a",{"href":172},"\u002Freact-nextjs-accessibility-patterns\u002F"," through modern hook abstractions, ensuring state-driven UIs remain perceivable and operable across React 18+ concurrent rendering and Next.js 13+ App Router architectures.",[166,175,176],{},[177,178,179],"strong",{},"Mapped WCAG Criteria:",[181,182,183,191,197,203],"ul",{},[184,185,186,190],"li",{},[187,188,189],"code",{},"1.3.1"," Info and Relationships",[184,192,193,196],{},[187,194,195],{},"2.1.1"," Keyboard",[184,198,199,202],{},[187,200,201],{},"2.4.3"," Focus Order",[184,204,205,208],{},[187,206,207],{},"4.1.2"," Name, Role, Value",[166,210,211],{},[177,212,213],{},"Key Implementation Principles:",[181,215,216,219,222,225],{},[184,217,218],{},"Encapsulate ARIA state logic in custom hooks to isolate side effects",[184,220,221],{},"Manage focus programmatically without breaking native tab order",[184,223,224],{},"Decouple rendering cycles from screen reader polling intervals",[184,226,227],{},"Align hook lifecycles with React 18 automatic batching and concurrent features",[229,230],"hr",{},[232,233,235],"h2",{"id":234},"the-role-of-custom-hooks-in-a11y-architecture","The Role of Custom Hooks in A11y Architecture",[166,237,238,239,242],{},"Higher-Order Components (HOCs) and render props historically introduced unnecessary wrapper depth, complicating DOM traversal for assistive technologies and increasing prop-drilling overhead. Custom hooks provide a cleaner separation of concerns: UI rendering remains declarative while accessibility logic executes as isolated side effects. This pattern is foundational when building scalable ",[170,240,79],{"href":241},"\u002Freact-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002F",", where consistent compliance must be enforced across modals, dropdowns, and complex data grids.",[166,244,245,246,249,250,252],{},"By leveraging TypeScript generics and strict return contracts, hooks guarantee type-safe ARIA attribute injection. This eliminates runtime mismatches between state and ",[187,247,248],{},"aria-*"," values, directly satisfying ",[187,251,207],{}," Name, Role, Value.",[166,254,255,258],{},[177,256,257],{},"Testing Hook:"," Verify hook isolation does not trigger unnecessary re-renders during focus transitions. Use the React DevTools Profiler to measure render cost and confirm that state updates only propagate when ARIA values actually change.",[229,260],{},[232,262,264,265],{"id":263},"managing-dynamic-state-announcements-with-usearialive","Managing Dynamic State Announcements with ",[187,266,267],{},"useAriaLive",[166,269,270,271,274,275,279],{},"Screen readers rely on ",[187,272,273],{},"aria-live"," regions to announce asynchronous state changes. In concurrent React, rapid state updates can cause announcement spam or race conditions where older messages overwrite newer ones before the AT processes them. Additionally, ",[170,276,278],{"href":277},"\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002F","Next.js App Router & A11y"," hydration mismatches frequently disrupt live region initialization if server-rendered attributes diverge from client state.",[166,281,282],{},"A production-ready hook must queue announcements, debounce rapid updates, and gracefully handle hydration sync.",[284,285,287,288],"h3",{"id":286},"implementation-usearialive","Implementation: ",[187,289,267],{},[291,292,297],"pre",{"className":293,"code":294,"language":295,"meta":296,"style":296},"language-tsx shiki shiki-themes github-light github-dark","import { useState, useEffect, useRef, useCallback } from 'react';\n\ntype Politeness = 'polite' | 'assertive';\n\nexport interface AriaLiveHook {\n announce: (message: string) => void;\n LiveRegion: React.FC;\n}\n\nexport function useAriaLive(politeness: Politeness = 'polite'): AriaLiveHook {\n const [currentMessage, setCurrentMessage] = useState('');\n const queueRef = useRef\u003Cstring[]>([]);\n const isProcessingRef = useRef(false);\n const timeoutRef = useRef\u003CReturnType\u003Ctypeof setTimeout> | null>(null);\n\n const announce = useCallback((msg: string) => {\n queueRef.current.push(msg);\n if (!isProcessingRef.current) processQueue();\n }, []);\n\n const processQueue = useCallback(() => {\n if (queueRef.current.length === 0) {\n isProcessingRef.current = false;\n return;\n }\n isProcessingRef.current = true;\n const next = queueRef.current.shift()!;\n setCurrentMessage(next);\n\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n \u002F\u002F Debounce to prevent screen reader interruption during rapid updates\n timeoutRef.current = setTimeout(() => {\n setCurrentMessage('');\n processQueue();\n }, 500);\n }, []);\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n };\n }, []);\n\n const LiveRegion = useCallback(() => (\n \u003Cdiv\n role=\"status\"\n aria-live={politeness}\n aria-atomic=\"true\"\n style={{ position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', clip: 'rect(0,0,0,0)' }}\n suppressHydrationWarning \u002F\u002F Prevents hydration mismatch in Next.js\n >\n {currentMessage}\n \u003C\u002Fdiv>\n ), [currentMessage, politeness]);\n\n return { announce, LiveRegion };\n}\n","tsx","",[187,298,299,322,329,353,358,373,406,425,431,436,470,505,527,546,585,590,618,630,650,656,661,680,700,713,721,727,739,761,770,775,789,796,813,824,831,842,847,852,864,876,887,893,898,903,921,931,942,953,964,1004,1013,1019,1025,1037,1043,1048,1056],{"__ignoreMap":296},[300,301,304,308,312,315,319],"span",{"class":302,"line":303},"line",1,[300,305,307],{"class":306},"szBVR","import",[300,309,311],{"class":310},"sVt8B"," { useState, useEffect, useRef, useCallback } ",[300,313,314],{"class":306},"from",[300,316,318],{"class":317},"sZZnC"," 'react'",[300,320,321],{"class":310},";\n",[300,323,325],{"class":302,"line":324},2,[300,326,328],{"emptyLinePlaceholder":327},true,"\n",[300,330,332,335,339,342,345,348,351],{"class":302,"line":331},3,[300,333,334],{"class":306},"type",[300,336,338],{"class":337},"sScJk"," Politeness",[300,340,341],{"class":306}," =",[300,343,344],{"class":317}," 'polite'",[300,346,347],{"class":306}," |",[300,349,350],{"class":317}," 'assertive'",[300,352,321],{"class":310},[300,354,356],{"class":302,"line":355},4,[300,357,328],{"emptyLinePlaceholder":327},[300,359,361,364,367,370],{"class":302,"line":360},5,[300,362,363],{"class":306},"export",[300,365,366],{"class":306}," interface",[300,368,369],{"class":337}," AriaLiveHook",[300,371,372],{"class":310}," {\n",[300,374,376,379,382,385,389,391,395,398,401,404],{"class":302,"line":375},6,[300,377,378],{"class":337}," announce",[300,380,381],{"class":306},":",[300,383,384],{"class":310}," (",[300,386,388],{"class":387},"s4XuR","message",[300,390,381],{"class":306},[300,392,394],{"class":393},"sj4cs"," string",[300,396,397],{"class":310},") ",[300,399,400],{"class":306},"=>",[300,402,403],{"class":393}," void",[300,405,321],{"class":310},[300,407,409,412,414,417,420,423],{"class":302,"line":408},7,[300,410,411],{"class":387}," LiveRegion",[300,413,381],{"class":306},[300,415,416],{"class":337}," React",[300,418,419],{"class":310},".",[300,421,422],{"class":337},"FC",[300,424,321],{"class":310},[300,426,428],{"class":302,"line":427},8,[300,429,430],{"class":310},"}\n",[300,432,434],{"class":302,"line":433},9,[300,435,328],{"emptyLinePlaceholder":327},[300,437,439,441,444,447,450,453,455,457,459,461,464,466,468],{"class":302,"line":438},10,[300,440,363],{"class":306},[300,442,443],{"class":306}," function",[300,445,446],{"class":337}," useAriaLive",[300,448,449],{"class":310},"(",[300,451,452],{"class":387},"politeness",[300,454,381],{"class":306},[300,456,338],{"class":337},[300,458,341],{"class":306},[300,460,344],{"class":317},[300,462,463],{"class":310},")",[300,465,381],{"class":306},[300,467,369],{"class":337},[300,469,372],{"class":310},[300,471,473,476,479,482,485,488,491,494,497,499,502],{"class":302,"line":472},11,[300,474,475],{"class":306}," const",[300,477,478],{"class":310}," [",[300,480,481],{"class":393},"currentMessage",[300,483,484],{"class":310},", ",[300,486,487],{"class":393},"setCurrentMessage",[300,489,490],{"class":310},"] ",[300,492,493],{"class":306},"=",[300,495,496],{"class":337}," useState",[300,498,449],{"class":310},[300,500,501],{"class":317},"''",[300,503,504],{"class":310},");\n",[300,506,508,510,513,515,518,521,524],{"class":302,"line":507},12,[300,509,475],{"class":306},[300,511,512],{"class":393}," queueRef",[300,514,341],{"class":306},[300,516,517],{"class":337}," useRef",[300,519,520],{"class":310},"\u003C",[300,522,523],{"class":393},"string",[300,525,526],{"class":310},"[]>([]);\n",[300,528,530,532,535,537,539,541,544],{"class":302,"line":529},13,[300,531,475],{"class":306},[300,533,534],{"class":393}," isProcessingRef",[300,536,341],{"class":306},[300,538,517],{"class":337},[300,540,449],{"class":310},[300,542,543],{"class":393},"false",[300,545,504],{"class":310},[300,547,549,551,554,556,558,560,563,565,568,571,574,577,580,583],{"class":302,"line":548},14,[300,550,475],{"class":306},[300,552,553],{"class":393}," timeoutRef",[300,555,341],{"class":306},[300,557,517],{"class":337},[300,559,520],{"class":310},[300,561,562],{"class":337},"ReturnType",[300,564,520],{"class":310},[300,566,567],{"class":306},"typeof",[300,569,570],{"class":310}," setTimeout> ",[300,572,573],{"class":306},"|",[300,575,576],{"class":393}," null",[300,578,579],{"class":310},">(",[300,581,582],{"class":393},"null",[300,584,504],{"class":310},[300,586,588],{"class":302,"line":587},15,[300,589,328],{"emptyLinePlaceholder":327},[300,591,593,595,597,599,602,605,608,610,612,614,616],{"class":302,"line":592},16,[300,594,475],{"class":306},[300,596,378],{"class":393},[300,598,341],{"class":306},[300,600,601],{"class":337}," useCallback",[300,603,604],{"class":310},"((",[300,606,607],{"class":387},"msg",[300,609,381],{"class":306},[300,611,394],{"class":393},[300,613,397],{"class":310},[300,615,400],{"class":306},[300,617,372],{"class":310},[300,619,621,624,627],{"class":302,"line":620},17,[300,622,623],{"class":310}," queueRef.current.",[300,625,626],{"class":337},"push",[300,628,629],{"class":310},"(msg);\n",[300,631,633,636,638,641,644,647],{"class":302,"line":632},18,[300,634,635],{"class":306}," if",[300,637,384],{"class":310},[300,639,640],{"class":306},"!",[300,642,643],{"class":310},"isProcessingRef.current) ",[300,645,646],{"class":337},"processQueue",[300,648,649],{"class":310},"();\n",[300,651,653],{"class":302,"line":652},19,[300,654,655],{"class":310}," }, []);\n",[300,657,659],{"class":302,"line":658},20,[300,660,328],{"emptyLinePlaceholder":327},[300,662,664,666,669,671,673,676,678],{"class":302,"line":663},21,[300,665,475],{"class":306},[300,667,668],{"class":393}," processQueue",[300,670,341],{"class":306},[300,672,601],{"class":337},[300,674,675],{"class":310},"(() ",[300,677,400],{"class":306},[300,679,372],{"class":310},[300,681,683,685,688,691,694,697],{"class":302,"line":682},22,[300,684,635],{"class":306},[300,686,687],{"class":310}," (queueRef.current.",[300,689,690],{"class":393},"length",[300,692,693],{"class":306}," ===",[300,695,696],{"class":393}," 0",[300,698,699],{"class":310},") {\n",[300,701,703,706,708,711],{"class":302,"line":702},23,[300,704,705],{"class":310}," isProcessingRef.current ",[300,707,493],{"class":306},[300,709,710],{"class":393}," false",[300,712,321],{"class":310},[300,714,716,719],{"class":302,"line":715},24,[300,717,718],{"class":306}," return",[300,720,321],{"class":310},[300,722,724],{"class":302,"line":723},25,[300,725,726],{"class":310}," }\n",[300,728,730,732,734,737],{"class":302,"line":729},26,[300,731,705],{"class":310},[300,733,493],{"class":306},[300,735,736],{"class":393}," true",[300,738,321],{"class":310},[300,740,742,744,747,749,751,754,757,759],{"class":302,"line":741},27,[300,743,475],{"class":306},[300,745,746],{"class":393}," next",[300,748,341],{"class":306},[300,750,623],{"class":310},[300,752,753],{"class":337},"shift",[300,755,756],{"class":310},"()",[300,758,640],{"class":306},[300,760,321],{"class":310},[300,762,764,767],{"class":302,"line":763},28,[300,765,766],{"class":337}," setCurrentMessage",[300,768,769],{"class":310},"(next);\n",[300,771,773],{"class":302,"line":772},29,[300,774,328],{"emptyLinePlaceholder":327},[300,776,778,780,783,786],{"class":302,"line":777},30,[300,779,635],{"class":306},[300,781,782],{"class":310}," (timeoutRef.current) ",[300,784,785],{"class":337},"clearTimeout",[300,787,788],{"class":310},"(timeoutRef.current);\n",[300,790,792],{"class":302,"line":791},31,[300,793,795],{"class":794},"sJ8bj"," \u002F\u002F Debounce to prevent screen reader interruption during rapid updates\n",[300,797,799,802,804,807,809,811],{"class":302,"line":798},32,[300,800,801],{"class":310}," timeoutRef.current ",[300,803,493],{"class":306},[300,805,806],{"class":337}," setTimeout",[300,808,675],{"class":310},[300,810,400],{"class":306},[300,812,372],{"class":310},[300,814,816,818,820,822],{"class":302,"line":815},33,[300,817,766],{"class":337},[300,819,449],{"class":310},[300,821,501],{"class":317},[300,823,504],{"class":310},[300,825,827,829],{"class":302,"line":826},34,[300,828,668],{"class":337},[300,830,649],{"class":310},[300,832,834,837,840],{"class":302,"line":833},35,[300,835,836],{"class":310}," }, ",[300,838,839],{"class":393},"500",[300,841,504],{"class":310},[300,843,845],{"class":302,"line":844},36,[300,846,655],{"class":310},[300,848,850],{"class":302,"line":849},37,[300,851,328],{"emptyLinePlaceholder":327},[300,853,855,858,860,862],{"class":302,"line":854},38,[300,856,857],{"class":337}," useEffect",[300,859,675],{"class":310},[300,861,400],{"class":306},[300,863,372],{"class":310},[300,865,867,869,872,874],{"class":302,"line":866},39,[300,868,718],{"class":306},[300,870,871],{"class":310}," () ",[300,873,400],{"class":306},[300,875,372],{"class":310},[300,877,879,881,883,885],{"class":302,"line":878},40,[300,880,635],{"class":306},[300,882,782],{"class":310},[300,884,785],{"class":337},[300,886,788],{"class":310},[300,888,890],{"class":302,"line":889},41,[300,891,892],{"class":310}," };\n",[300,894,896],{"class":302,"line":895},42,[300,897,655],{"class":310},[300,899,901],{"class":302,"line":900},43,[300,902,328],{"emptyLinePlaceholder":327},[300,904,906,908,910,912,914,916,918],{"class":302,"line":905},44,[300,907,475],{"class":306},[300,909,411],{"class":393},[300,911,341],{"class":306},[300,913,601],{"class":337},[300,915,675],{"class":310},[300,917,400],{"class":306},[300,919,920],{"class":310}," (\n",[300,922,924,927],{"class":302,"line":923},45,[300,925,926],{"class":310}," \u003C",[300,928,930],{"class":929},"s9eBZ","div\n",[300,932,934,937,939],{"class":302,"line":933},46,[300,935,936],{"class":337}," role",[300,938,493],{"class":306},[300,940,941],{"class":317},"\"status\"\n",[300,943,945,948,950],{"class":302,"line":944},47,[300,946,947],{"class":337}," aria-live",[300,949,493],{"class":306},[300,951,952],{"class":310},"{politeness}\n",[300,954,956,959,961],{"class":302,"line":955},48,[300,957,958],{"class":337}," aria-atomic",[300,960,493],{"class":306},[300,962,963],{"class":317},"\"true\"\n",[300,965,967,970,972,975,978,981,984,987,989,992,995,998,1001],{"class":302,"line":966},49,[300,968,969],{"class":337}," style",[300,971,493],{"class":306},[300,973,974],{"class":310},"{{ position: ",[300,976,977],{"class":317},"'absolute'",[300,979,980],{"class":310},", width: ",[300,982,983],{"class":317},"'1px'",[300,985,986],{"class":310},", height: ",[300,988,983],{"class":317},[300,990,991],{"class":310},", overflow: ",[300,993,994],{"class":317},"'hidden'",[300,996,997],{"class":310},", clip: ",[300,999,1000],{"class":317},"'rect(0,0,0,0)'",[300,1002,1003],{"class":310}," }}\n",[300,1005,1007,1010],{"class":302,"line":1006},50,[300,1008,1009],{"class":337}," suppressHydrationWarning",[300,1011,1012],{"class":794}," \u002F\u002F Prevents hydration mismatch in Next.js\n",[300,1014,1016],{"class":302,"line":1015},51,[300,1017,1018],{"class":310}," >\n",[300,1020,1022],{"class":302,"line":1021},52,[300,1023,1024],{"class":310}," {currentMessage}\n",[300,1026,1028,1031,1034],{"class":302,"line":1027},53,[300,1029,1030],{"class":310}," \u003C\u002F",[300,1032,1033],{"class":929},"div",[300,1035,1036],{"class":310},">\n",[300,1038,1040],{"class":302,"line":1039},54,[300,1041,1042],{"class":310}," ), [currentMessage, politeness]);\n",[300,1044,1046],{"class":302,"line":1045},55,[300,1047,328],{"emptyLinePlaceholder":327},[300,1049,1051,1053],{"class":302,"line":1050},56,[300,1052,718],{"class":306},[300,1054,1055],{"class":310}," { announce, LiveRegion };\n",[300,1057,1059],{"class":302,"line":1058},57,[300,1060,430],{"class":310},[166,1062,1063,1065],{},[177,1064,257],{}," Validate announcement timing with VoiceOver (macOS) and NVDA (Windows). Confirm that rapid state changes batch correctly and that hydration errors do not appear in the Next.js console.",[229,1067],{},[232,1069,1071],{"id":1070},"focus-management-keyboard-navigation-hooks","Focus Management & Keyboard Navigation Hooks",[166,1073,1074,1075,68,1078,1081,1082,1085,1086,1090,1091,1094],{},"Focus trapping is critical for modal dialogs, drawers, and dropdown menus. The challenge lies in scoping ",[187,1076,1077],{},"Tab",[187,1079,1080],{},"Shift+Tab"," navigation to a specific subtree while preserving escape-key behavior and restoring focus to the trigger element on unmount. When components render via ",[187,1083,1084],{},"createPortal",", standard DOM queries fail because the portal's DOM exists outside the parent tree. Addressing ",[170,1087,1089],{"href":1088},"\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Ffixing-focus-trap-issues-in-react-portals\u002F","Fixing focus trap issues in React portals"," requires explicit ref scoping and ",[187,1092,1093],{},"requestAnimationFrame"," scheduling to avoid layout thrashing.",[284,1096,287,1098],{"id":1097},"implementation-usefocustrap",[187,1099,1100],{},"useFocusTrap",[291,1102,1104],{"className":293,"code":1103,"language":295,"meta":296,"style":296},"import { useEffect, useRef, useCallback } from 'react';\n\nconst FOCUSABLE_SELECTORS = [\n 'a[href]', 'button:not([disabled])', 'input:not([disabled])',\n 'select:not([disabled])', 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])', '[contenteditable]'\n].join(', ');\n\nexport function useFocusTrap(\n containerRef: React.RefObject\u003CHTMLElement | null>, \n isActive: boolean\n) {\n const previousFocusRef = useRef\u003CHTMLElement | null>(null);\n\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (!isActive || !containerRef.current) return;\n\n if (e.key === 'Escape') {\n previousFocusRef.current?.focus();\n return;\n }\n\n if (e.key === 'Tab') {\n const focusableElements = Array.from(\n containerRef.current.querySelectorAll\u003CHTMLElement>(FOCUSABLE_SELECTORS)\n );\n if (focusableElements.length === 0) return;\n\n const firstEl = focusableElements[0];\n const lastEl = focusableElements[focusableElements.length - 1];\n\n if (e.shiftKey && document.activeElement === firstEl) {\n e.preventDefault();\n lastEl.focus();\n } else if (!e.shiftKey && document.activeElement === lastEl) {\n e.preventDefault();\n firstEl.focus();\n }\n }\n }, [isActive, containerRef]);\n\n useEffect(() => {\n if (!isActive || !containerRef.current) return;\n\n previousFocusRef.current = document.activeElement as HTMLElement;\n const focusable = containerRef.current.querySelector\u003CHTMLElement>(FOCUSABLE_SELECTORS);\n \n \u002F\u002F Defer focus to post-paint to prevent React 18 StrictMode double-invocation issues\n requestAnimationFrame(() => focusable?.focus());\n\n document.addEventListener('keydown', handleKeyDown);\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n previousFocusRef.current?.focus();\n };\n }, [isActive, containerRef, handleKeyDown]);\n}\n",[187,1105,1106,1119,1123,1136,1154,1166,1176,1191,1195,1207,1233,1243,1247,1272,1276,1303,1328,1332,1347,1357,1363,1367,1371,1384,1400,1420,1425,1444,1448,1466,1488,1492,1510,1520,1529,1555,1563,1572,1576,1580,1585,1589,1599,1619,1623,1640,1664,1669,1674,1691,1695,1711,1721,1734,1742,1746,1751],{"__ignoreMap":296},[300,1107,1108,1110,1113,1115,1117],{"class":302,"line":303},[300,1109,307],{"class":306},[300,1111,1112],{"class":310}," { useEffect, useRef, useCallback } ",[300,1114,314],{"class":306},[300,1116,318],{"class":317},[300,1118,321],{"class":310},[300,1120,1121],{"class":302,"line":324},[300,1122,328],{"emptyLinePlaceholder":327},[300,1124,1125,1128,1131,1133],{"class":302,"line":331},[300,1126,1127],{"class":306},"const",[300,1129,1130],{"class":393}," FOCUSABLE_SELECTORS",[300,1132,341],{"class":306},[300,1134,1135],{"class":310}," [\n",[300,1137,1138,1141,1143,1146,1148,1151],{"class":302,"line":355},[300,1139,1140],{"class":317}," 'a[href]'",[300,1142,484],{"class":310},[300,1144,1145],{"class":317},"'button:not([disabled])'",[300,1147,484],{"class":310},[300,1149,1150],{"class":317},"'input:not([disabled])'",[300,1152,1153],{"class":310},",\n",[300,1155,1156,1159,1161,1164],{"class":302,"line":360},[300,1157,1158],{"class":317}," 'select:not([disabled])'",[300,1160,484],{"class":310},[300,1162,1163],{"class":317},"'textarea:not([disabled])'",[300,1165,1153],{"class":310},[300,1167,1168,1171,1173],{"class":302,"line":375},[300,1169,1170],{"class":317}," '[tabindex]:not([tabindex=\"-1\"])'",[300,1172,484],{"class":310},[300,1174,1175],{"class":317},"'[contenteditable]'\n",[300,1177,1178,1181,1184,1186,1189],{"class":302,"line":408},[300,1179,1180],{"class":310},"].",[300,1182,1183],{"class":337},"join",[300,1185,449],{"class":310},[300,1187,1188],{"class":317},"', '",[300,1190,504],{"class":310},[300,1192,1193],{"class":302,"line":427},[300,1194,328],{"emptyLinePlaceholder":327},[300,1196,1197,1199,1201,1204],{"class":302,"line":433},[300,1198,363],{"class":306},[300,1200,443],{"class":306},[300,1202,1203],{"class":337}," useFocusTrap",[300,1205,1206],{"class":310},"(\n",[300,1208,1209,1212,1214,1216,1218,1221,1223,1226,1228,1230],{"class":302,"line":438},[300,1210,1211],{"class":387}," containerRef",[300,1213,381],{"class":306},[300,1215,416],{"class":337},[300,1217,419],{"class":310},[300,1219,1220],{"class":337},"RefObject",[300,1222,520],{"class":310},[300,1224,1225],{"class":337},"HTMLElement",[300,1227,347],{"class":306},[300,1229,576],{"class":393},[300,1231,1232],{"class":310},">, \n",[300,1234,1235,1238,1240],{"class":302,"line":472},[300,1236,1237],{"class":387}," isActive",[300,1239,381],{"class":306},[300,1241,1242],{"class":393}," boolean\n",[300,1244,1245],{"class":302,"line":507},[300,1246,699],{"class":310},[300,1248,1249,1251,1254,1256,1258,1260,1262,1264,1266,1268,1270],{"class":302,"line":529},[300,1250,475],{"class":306},[300,1252,1253],{"class":393}," previousFocusRef",[300,1255,341],{"class":306},[300,1257,517],{"class":337},[300,1259,520],{"class":310},[300,1261,1225],{"class":337},[300,1263,347],{"class":306},[300,1265,576],{"class":393},[300,1267,579],{"class":310},[300,1269,582],{"class":393},[300,1271,504],{"class":310},[300,1273,1274],{"class":302,"line":548},[300,1275,328],{"emptyLinePlaceholder":327},[300,1277,1278,1280,1283,1285,1287,1289,1292,1294,1297,1299,1301],{"class":302,"line":587},[300,1279,475],{"class":306},[300,1281,1282],{"class":393}," handleKeyDown",[300,1284,341],{"class":306},[300,1286,601],{"class":337},[300,1288,604],{"class":310},[300,1290,1291],{"class":387},"e",[300,1293,381],{"class":306},[300,1295,1296],{"class":337}," KeyboardEvent",[300,1298,397],{"class":310},[300,1300,400],{"class":306},[300,1302,372],{"class":310},[300,1304,1305,1307,1309,1311,1314,1317,1320,1323,1326],{"class":302,"line":592},[300,1306,635],{"class":306},[300,1308,384],{"class":310},[300,1310,640],{"class":306},[300,1312,1313],{"class":310},"isActive ",[300,1315,1316],{"class":306},"||",[300,1318,1319],{"class":306}," !",[300,1321,1322],{"class":310},"containerRef.current) ",[300,1324,1325],{"class":306},"return",[300,1327,321],{"class":310},[300,1329,1330],{"class":302,"line":620},[300,1331,328],{"emptyLinePlaceholder":327},[300,1333,1334,1336,1339,1342,1345],{"class":302,"line":632},[300,1335,635],{"class":306},[300,1337,1338],{"class":310}," (e.key ",[300,1340,1341],{"class":306},"===",[300,1343,1344],{"class":317}," 'Escape'",[300,1346,699],{"class":310},[300,1348,1349,1352,1355],{"class":302,"line":652},[300,1350,1351],{"class":310}," previousFocusRef.current?.",[300,1353,1354],{"class":337},"focus",[300,1356,649],{"class":310},[300,1358,1359,1361],{"class":302,"line":658},[300,1360,718],{"class":306},[300,1362,321],{"class":310},[300,1364,1365],{"class":302,"line":663},[300,1366,726],{"class":310},[300,1368,1369],{"class":302,"line":682},[300,1370,328],{"emptyLinePlaceholder":327},[300,1372,1373,1375,1377,1379,1382],{"class":302,"line":702},[300,1374,635],{"class":306},[300,1376,1338],{"class":310},[300,1378,1341],{"class":306},[300,1380,1381],{"class":317}," 'Tab'",[300,1383,699],{"class":310},[300,1385,1386,1388,1391,1393,1396,1398],{"class":302,"line":715},[300,1387,475],{"class":306},[300,1389,1390],{"class":393}," focusableElements",[300,1392,341],{"class":306},[300,1394,1395],{"class":310}," Array.",[300,1397,314],{"class":337},[300,1399,1206],{"class":310},[300,1401,1402,1405,1408,1410,1412,1414,1417],{"class":302,"line":723},[300,1403,1404],{"class":310}," containerRef.current.",[300,1406,1407],{"class":337},"querySelectorAll",[300,1409,520],{"class":310},[300,1411,1225],{"class":337},[300,1413,579],{"class":310},[300,1415,1416],{"class":393},"FOCUSABLE_SELECTORS",[300,1418,1419],{"class":310},")\n",[300,1421,1422],{"class":302,"line":729},[300,1423,1424],{"class":310}," );\n",[300,1426,1427,1429,1432,1434,1436,1438,1440,1442],{"class":302,"line":741},[300,1428,635],{"class":306},[300,1430,1431],{"class":310}," (focusableElements.",[300,1433,690],{"class":393},[300,1435,693],{"class":306},[300,1437,696],{"class":393},[300,1439,397],{"class":310},[300,1441,1325],{"class":306},[300,1443,321],{"class":310},[300,1445,1446],{"class":302,"line":763},[300,1447,328],{"emptyLinePlaceholder":327},[300,1449,1450,1452,1455,1457,1460,1463],{"class":302,"line":772},[300,1451,475],{"class":306},[300,1453,1454],{"class":393}," firstEl",[300,1456,341],{"class":306},[300,1458,1459],{"class":310}," focusableElements[",[300,1461,1462],{"class":393},"0",[300,1464,1465],{"class":310},"];\n",[300,1467,1468,1470,1473,1475,1478,1480,1483,1486],{"class":302,"line":777},[300,1469,475],{"class":306},[300,1471,1472],{"class":393}," lastEl",[300,1474,341],{"class":306},[300,1476,1477],{"class":310}," focusableElements[focusableElements.",[300,1479,690],{"class":393},[300,1481,1482],{"class":306}," -",[300,1484,1485],{"class":393}," 1",[300,1487,1465],{"class":310},[300,1489,1490],{"class":302,"line":791},[300,1491,328],{"emptyLinePlaceholder":327},[300,1493,1494,1496,1499,1502,1505,1507],{"class":302,"line":798},[300,1495,635],{"class":306},[300,1497,1498],{"class":310}," (e.shiftKey ",[300,1500,1501],{"class":306},"&&",[300,1503,1504],{"class":310}," document.activeElement ",[300,1506,1341],{"class":306},[300,1508,1509],{"class":310}," firstEl) {\n",[300,1511,1512,1515,1518],{"class":302,"line":815},[300,1513,1514],{"class":310}," e.",[300,1516,1517],{"class":337},"preventDefault",[300,1519,649],{"class":310},[300,1521,1522,1525,1527],{"class":302,"line":826},[300,1523,1524],{"class":310}," lastEl.",[300,1526,1354],{"class":337},[300,1528,649],{"class":310},[300,1530,1531,1534,1537,1539,1541,1543,1546,1548,1550,1552],{"class":302,"line":833},[300,1532,1533],{"class":310}," } ",[300,1535,1536],{"class":306},"else",[300,1538,635],{"class":306},[300,1540,384],{"class":310},[300,1542,640],{"class":306},[300,1544,1545],{"class":310},"e.shiftKey ",[300,1547,1501],{"class":306},[300,1549,1504],{"class":310},[300,1551,1341],{"class":306},[300,1553,1554],{"class":310}," lastEl) {\n",[300,1556,1557,1559,1561],{"class":302,"line":844},[300,1558,1514],{"class":310},[300,1560,1517],{"class":337},[300,1562,649],{"class":310},[300,1564,1565,1568,1570],{"class":302,"line":849},[300,1566,1567],{"class":310}," firstEl.",[300,1569,1354],{"class":337},[300,1571,649],{"class":310},[300,1573,1574],{"class":302,"line":854},[300,1575,726],{"class":310},[300,1577,1578],{"class":302,"line":866},[300,1579,726],{"class":310},[300,1581,1582],{"class":302,"line":878},[300,1583,1584],{"class":310}," }, [isActive, containerRef]);\n",[300,1586,1587],{"class":302,"line":889},[300,1588,328],{"emptyLinePlaceholder":327},[300,1590,1591,1593,1595,1597],{"class":302,"line":895},[300,1592,857],{"class":337},[300,1594,675],{"class":310},[300,1596,400],{"class":306},[300,1598,372],{"class":310},[300,1600,1601,1603,1605,1607,1609,1611,1613,1615,1617],{"class":302,"line":900},[300,1602,635],{"class":306},[300,1604,384],{"class":310},[300,1606,640],{"class":306},[300,1608,1313],{"class":310},[300,1610,1316],{"class":306},[300,1612,1319],{"class":306},[300,1614,1322],{"class":310},[300,1616,1325],{"class":306},[300,1618,321],{"class":310},[300,1620,1621],{"class":302,"line":905},[300,1622,328],{"emptyLinePlaceholder":327},[300,1624,1625,1628,1630,1632,1635,1638],{"class":302,"line":923},[300,1626,1627],{"class":310}," previousFocusRef.current ",[300,1629,493],{"class":306},[300,1631,1504],{"class":310},[300,1633,1634],{"class":306},"as",[300,1636,1637],{"class":337}," HTMLElement",[300,1639,321],{"class":310},[300,1641,1642,1644,1647,1649,1651,1654,1656,1658,1660,1662],{"class":302,"line":933},[300,1643,475],{"class":306},[300,1645,1646],{"class":393}," focusable",[300,1648,341],{"class":306},[300,1650,1404],{"class":310},[300,1652,1653],{"class":337},"querySelector",[300,1655,520],{"class":310},[300,1657,1225],{"class":337},[300,1659,579],{"class":310},[300,1661,1416],{"class":393},[300,1663,504],{"class":310},[300,1665,1666],{"class":302,"line":944},[300,1667,1668],{"class":310}," \n",[300,1670,1671],{"class":302,"line":955},[300,1672,1673],{"class":794}," \u002F\u002F Defer focus to post-paint to prevent React 18 StrictMode double-invocation issues\n",[300,1675,1676,1679,1681,1683,1686,1688],{"class":302,"line":966},[300,1677,1678],{"class":337}," requestAnimationFrame",[300,1680,675],{"class":310},[300,1682,400],{"class":306},[300,1684,1685],{"class":310}," focusable?.",[300,1687,1354],{"class":337},[300,1689,1690],{"class":310},"());\n",[300,1692,1693],{"class":302,"line":1006},[300,1694,328],{"emptyLinePlaceholder":327},[300,1696,1697,1700,1703,1705,1708],{"class":302,"line":1015},[300,1698,1699],{"class":310}," document.",[300,1701,1702],{"class":337},"addEventListener",[300,1704,449],{"class":310},[300,1706,1707],{"class":317},"'keydown'",[300,1709,1710],{"class":310},", handleKeyDown);\n",[300,1712,1713,1715,1717,1719],{"class":302,"line":1021},[300,1714,718],{"class":306},[300,1716,871],{"class":310},[300,1718,400],{"class":306},[300,1720,372],{"class":310},[300,1722,1723,1725,1728,1730,1732],{"class":302,"line":1027},[300,1724,1699],{"class":310},[300,1726,1727],{"class":337},"removeEventListener",[300,1729,449],{"class":310},[300,1731,1707],{"class":317},[300,1733,1710],{"class":310},[300,1735,1736,1738,1740],{"class":302,"line":1039},[300,1737,1351],{"class":310},[300,1739,1354],{"class":337},[300,1741,649],{"class":310},[300,1743,1744],{"class":302,"line":1045},[300,1745,892],{"class":310},[300,1747,1748],{"class":302,"line":1050},[300,1749,1750],{"class":310}," }, [isActive, containerRef, handleKeyDown]);\n",[300,1752,1753],{"class":302,"line":1058},[300,1754,430],{"class":310},[166,1756,1757,1759,1760,1763],{},[177,1758,257],{}," Validate focus loop boundaries and ",[187,1761,1762],{},"Escape"," key behavior across Safari, Chrome, and Firefox. Ensure focus reliably returns to the trigger element on close, even when nested modals are dismissed out of order.",[229,1765],{},[232,1767,1769],{"id":1768},"side-effects-screen-reader-compatibility","Side Effects & Screen Reader Compatibility",[166,1771,1772,1773,1777],{},"Improperly structured side effects are a primary cause of accessibility regressions. Stale closures in event listeners, unsynchronized dependency arrays, and blocking DOM mutations can cause screen readers to announce outdated state or skip focus targets entirely. ",[170,1774,1776],{"href":1775},"\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Fmaking-react-useeffect-accessible-for-screen-readers\u002F","Making React useEffect accessible for screen readers"," requires strict alignment between React's render phase and the browser's accessibility tree updates.",[166,1779,1780,1781,1784,1785,1788],{},"Use ",[187,1782,1783],{},"useLayoutEffect"," for synchronous DOM measurements or immediate focus assignment to prevent visual jumps. Reserve ",[187,1786,1787],{},"useEffect"," for non-urgent ARIA updates and live region polling to avoid blocking the main thread.",[284,1790,287,1792],{"id":1791},"implementation-useaccessiblestate",[187,1793,1794],{},"useAccessibleState",[291,1796,1798],{"className":293,"code":1797,"language":295,"meta":296,"style":296},"import { useMemo } from 'react';\n\ntype AccessibleStateConfig = {\n expanded?: boolean;\n checked?: boolean;\n disabled?: boolean;\n loading?: boolean;\n label?: string;\n};\n\nexport function useAccessibleState(config: AccessibleStateConfig) {\n return useMemo(() => {\n const ariaProps: Record\u003Cstring, string | boolean | undefined> = {};\n\n if (typeof config.expanded === 'boolean') ariaProps['aria-expanded'] = config.expanded;\n if (typeof config.checked === 'boolean') ariaProps['aria-checked'] = config.checked;\n if (typeof config.disabled === 'boolean') ariaProps['aria-disabled'] = config.disabled;\n \n if (config.loading) {\n ariaProps['aria-busy'] = true;\n ariaProps['aria-live'] = 'polite';\n }\n if (config.label) ariaProps['aria-label'] = config.label;\n\n return ariaProps;\n }, [config.expanded, config.checked, config.disabled, config.loading, config.label]);\n}\n",[187,1799,1800,1813,1817,1828,1841,1852,1863,1874,1885,1890,1894,1914,1927,1964,1968,1997,2024,2051,2055,2062,2078,2093,2097,2114,2118,2125,2130],{"__ignoreMap":296},[300,1801,1802,1804,1807,1809,1811],{"class":302,"line":303},[300,1803,307],{"class":306},[300,1805,1806],{"class":310}," { useMemo } ",[300,1808,314],{"class":306},[300,1810,318],{"class":317},[300,1812,321],{"class":310},[300,1814,1815],{"class":302,"line":324},[300,1816,328],{"emptyLinePlaceholder":327},[300,1818,1819,1821,1824,1826],{"class":302,"line":331},[300,1820,334],{"class":306},[300,1822,1823],{"class":337}," AccessibleStateConfig",[300,1825,341],{"class":306},[300,1827,372],{"class":310},[300,1829,1830,1833,1836,1839],{"class":302,"line":355},[300,1831,1832],{"class":387}," expanded",[300,1834,1835],{"class":306},"?:",[300,1837,1838],{"class":393}," boolean",[300,1840,321],{"class":310},[300,1842,1843,1846,1848,1850],{"class":302,"line":360},[300,1844,1845],{"class":387}," checked",[300,1847,1835],{"class":306},[300,1849,1838],{"class":393},[300,1851,321],{"class":310},[300,1853,1854,1857,1859,1861],{"class":302,"line":375},[300,1855,1856],{"class":387}," disabled",[300,1858,1835],{"class":306},[300,1860,1838],{"class":393},[300,1862,321],{"class":310},[300,1864,1865,1868,1870,1872],{"class":302,"line":408},[300,1866,1867],{"class":387}," loading",[300,1869,1835],{"class":306},[300,1871,1838],{"class":393},[300,1873,321],{"class":310},[300,1875,1876,1879,1881,1883],{"class":302,"line":427},[300,1877,1878],{"class":387}," label",[300,1880,1835],{"class":306},[300,1882,394],{"class":393},[300,1884,321],{"class":310},[300,1886,1887],{"class":302,"line":433},[300,1888,1889],{"class":310},"};\n",[300,1891,1892],{"class":302,"line":438},[300,1893,328],{"emptyLinePlaceholder":327},[300,1895,1896,1898,1900,1903,1905,1908,1910,1912],{"class":302,"line":472},[300,1897,363],{"class":306},[300,1899,443],{"class":306},[300,1901,1902],{"class":337}," useAccessibleState",[300,1904,449],{"class":310},[300,1906,1907],{"class":387},"config",[300,1909,381],{"class":306},[300,1911,1823],{"class":337},[300,1913,699],{"class":310},[300,1915,1916,1918,1921,1923,1925],{"class":302,"line":507},[300,1917,718],{"class":306},[300,1919,1920],{"class":337}," useMemo",[300,1922,675],{"class":310},[300,1924,400],{"class":306},[300,1926,372],{"class":310},[300,1928,1929,1931,1934,1936,1939,1941,1943,1945,1947,1949,1951,1953,1956,1959,1961],{"class":302,"line":529},[300,1930,475],{"class":306},[300,1932,1933],{"class":393}," ariaProps",[300,1935,381],{"class":306},[300,1937,1938],{"class":337}," Record",[300,1940,520],{"class":310},[300,1942,523],{"class":393},[300,1944,484],{"class":310},[300,1946,523],{"class":393},[300,1948,347],{"class":306},[300,1950,1838],{"class":393},[300,1952,347],{"class":306},[300,1954,1955],{"class":393}," undefined",[300,1957,1958],{"class":310},"> ",[300,1960,493],{"class":306},[300,1962,1963],{"class":310}," {};\n",[300,1965,1966],{"class":302,"line":548},[300,1967,328],{"emptyLinePlaceholder":327},[300,1969,1970,1972,1974,1976,1979,1981,1984,1987,1990,1992,1994],{"class":302,"line":587},[300,1971,635],{"class":306},[300,1973,384],{"class":310},[300,1975,567],{"class":306},[300,1977,1978],{"class":310}," config.expanded ",[300,1980,1341],{"class":306},[300,1982,1983],{"class":317}," 'boolean'",[300,1985,1986],{"class":310},") ariaProps[",[300,1988,1989],{"class":317},"'aria-expanded'",[300,1991,490],{"class":310},[300,1993,493],{"class":306},[300,1995,1996],{"class":310}," config.expanded;\n",[300,1998,1999,2001,2003,2005,2008,2010,2012,2014,2017,2019,2021],{"class":302,"line":592},[300,2000,635],{"class":306},[300,2002,384],{"class":310},[300,2004,567],{"class":306},[300,2006,2007],{"class":310}," config.checked ",[300,2009,1341],{"class":306},[300,2011,1983],{"class":317},[300,2013,1986],{"class":310},[300,2015,2016],{"class":317},"'aria-checked'",[300,2018,490],{"class":310},[300,2020,493],{"class":306},[300,2022,2023],{"class":310}," config.checked;\n",[300,2025,2026,2028,2030,2032,2035,2037,2039,2041,2044,2046,2048],{"class":302,"line":620},[300,2027,635],{"class":306},[300,2029,384],{"class":310},[300,2031,567],{"class":306},[300,2033,2034],{"class":310}," config.disabled ",[300,2036,1341],{"class":306},[300,2038,1983],{"class":317},[300,2040,1986],{"class":310},[300,2042,2043],{"class":317},"'aria-disabled'",[300,2045,490],{"class":310},[300,2047,493],{"class":306},[300,2049,2050],{"class":310}," config.disabled;\n",[300,2052,2053],{"class":302,"line":632},[300,2054,1668],{"class":310},[300,2056,2057,2059],{"class":302,"line":652},[300,2058,635],{"class":306},[300,2060,2061],{"class":310}," (config.loading) {\n",[300,2063,2064,2067,2070,2072,2074,2076],{"class":302,"line":658},[300,2065,2066],{"class":310}," ariaProps[",[300,2068,2069],{"class":317},"'aria-busy'",[300,2071,490],{"class":310},[300,2073,493],{"class":306},[300,2075,736],{"class":393},[300,2077,321],{"class":310},[300,2079,2080,2082,2085,2087,2089,2091],{"class":302,"line":663},[300,2081,2066],{"class":310},[300,2083,2084],{"class":317},"'aria-live'",[300,2086,490],{"class":310},[300,2088,493],{"class":306},[300,2090,344],{"class":317},[300,2092,321],{"class":310},[300,2094,2095],{"class":302,"line":682},[300,2096,726],{"class":310},[300,2098,2099,2101,2104,2107,2109,2111],{"class":302,"line":702},[300,2100,635],{"class":306},[300,2102,2103],{"class":310}," (config.label) ariaProps[",[300,2105,2106],{"class":317},"'aria-label'",[300,2108,490],{"class":310},[300,2110,493],{"class":306},[300,2112,2113],{"class":310}," config.label;\n",[300,2115,2116],{"class":302,"line":715},[300,2117,328],{"emptyLinePlaceholder":327},[300,2119,2120,2122],{"class":302,"line":723},[300,2121,718],{"class":306},[300,2123,2124],{"class":310}," ariaProps;\n",[300,2126,2127],{"class":302,"line":729},[300,2128,2129],{"class":310}," }, [config.expanded, config.checked, config.disabled, config.loading, config.label]);\n",[300,2131,2132],{"class":302,"line":741},[300,2133,430],{"class":310},[166,2135,2136,2138,2139,2142],{},[177,2137,257],{}," Audit with axe DevTools to catch focus loss during async state transitions. Verify that global ",[187,2140,2141],{},"keydown"," listeners are strictly removed on unmount to prevent memory leaks and cross-component interference.",[229,2144],{},[232,2146,2148],{"id":2147},"common-pitfalls","Common Pitfalls",[181,2150,2151,2166,2172,2199,2209],{},[184,2152,2153,2156,2157,2159,2160,2162,2163,2165],{},[177,2154,2155],{},"StrictMode Double-Firing:"," Overusing ",[187,2158,1787],{}," for focus management causes double-firing in React 18 StrictMode, leading to erratic focus jumps. Wrap imperative focus calls in ",[187,2161,1093],{}," or use ",[187,2164,1783],{}," with cleanup guards.",[184,2167,2168,2171],{},[177,2169,2170],{},"Automatic Batching Conflicts:"," Failing to account for React 18 automatic batching when updating live regions can merge multiple state changes into a single DOM update, causing screen readers to skip intermediate announcements.",[184,2173,2174,2180,2181,2184,2185,2188,2189,484,2192,2195,2196,2198],{},[177,2175,2176,2177,381],{},"Hardcoded ",[187,2178,2179],{},"tabindex"," Avoid ",[187,2182,2183],{},"tabindex=\"0\""," or ",[187,2186,2187],{},"tabindex=\"-1\""," on native interactive elements. Rely on semantic HTML (",[187,2190,2191],{},"\u003Cbutton>",[187,2193,2194],{},"\u003Ca>",") and use ",[187,2197,2179],{}," only for custom composite widgets.",[184,2200,2201,2204,2205,2208],{},[177,2202,2203],{},"Hydration Mismatches:"," Server-rendered ARIA attributes that diverge from initial client state trigger hydration warnings and break AT synchronization. Use ",[187,2206,2207],{},"suppressHydrationWarning"," or defer ARIA injection to client-side mounts.",[184,2210,2211,2214,2215,2218],{},[177,2212,2213],{},"Uncleaned Global Listeners:"," Forgetting to remove ",[187,2216,2217],{},"document.addEventListener('keydown', ...)"," in hook cleanup functions causes memory leaks and unexpected keyboard behavior across route transitions.",[229,2220],{},[232,2222,2224],{"id":2223},"faq","FAQ",[166,2226,2227,2235,2236,2238,2239,2241],{},[177,2228,2229,2230,2184,2232,2234],{},"Should I use ",[187,2231,1787],{},[187,2233,1783],{}," for accessibility focus management?","\nUse ",[187,2237,1783],{}," when focus must be set synchronously before the browser paints to prevent visual jumps or screen reader confusion. Use ",[187,2240,1787],{}," for non-urgent ARIA updates and live region announcements to avoid blocking the main thread.",[166,2243,2244,2247,2248,2251],{},[177,2245,2246],{},"How do custom accessibility hooks handle React Server Components?","\nRSCs cannot use hooks. Accessibility logic must be delegated to Client Components. Use the ",[187,2249,2250],{},"'use client'"," directive at the top of the component file, and pass server-fetched data as props to hook-driven client wrappers.",[166,2253,2254,2257,2258,2260],{},[177,2255,2256],{},"Can focus trap hooks work reliably inside React Portals?","\nYes, but the hook must query the portal's DOM subtree rather than the parent tree. Use a ref attached to the portal container and scope ",[187,2259,1653],{}," calls to that ref to ensure accurate focus boundary detection.",[166,2262,2263,2266],{},[177,2264,2265],{},"How do I prevent announcement spam during rapid state changes?","\nImplement a debounce or throttle mechanism inside the hook, or use a queue system that batches announcements. Only the latest state should be announced after a short delay (e.g., 300–500ms) to match screen reader processing speeds and prevent audio overlap.",[2268,2269,2270],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":296,"searchDepth":324,"depth":324,"links":2272},[2273,2274,2279,2283,2287,2288],{"id":234,"depth":324,"text":235},{"id":263,"depth":324,"text":2275,"children":2276},"Managing Dynamic State Announcements with useAriaLive",[2277],{"id":286,"depth":331,"text":2278},"Implementation: useAriaLive",{"id":1070,"depth":324,"text":1071,"children":2280},[2281],{"id":1097,"depth":331,"text":2282},"Implementation: useFocusTrap",{"id":1768,"depth":324,"text":1769,"children":2284},[2285],{"id":1791,"depth":331,"text":2286},"Implementation: useAccessibleState",{"id":2147,"depth":324,"text":2148},{"id":2223,"depth":324,"text":2224},null,"Use reusable React hooks to solve accessibility at scale, including focus control, live announcements, and keyboard interactions across complex UI state.","md",{},false,{"title":127,"description":2290},"zO0D2okE-vzxFF6ynSwoKOze9-9PhkKwrqfPFdGWEmw",[2297,2327,2328],{"title":5,"path":6,"stem":7,"children":2298},[2299,2300,2303,2306,2312,2318,2324],{"title":10,"path":6,"stem":11},{"title":13,"path":14,"stem":15,"children":2301},[2302],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":2304},[2305],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":2307},[2308,2309],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":2310},[2311],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":2313},[2314,2315],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":2316},[2317],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":2319},[2320,2321],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":2322},[2323],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":2325},[2326],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69},{"title":71,"path":72,"stem":73,"children":2329},[2330,2331,2337,2343,2346,2355,2364],{"title":76,"path":72,"stem":77},{"title":79,"path":80,"stem":81,"children":2332},[2333,2334],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":2335},[2336],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":2338},[2339,2340],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":2341},[2342],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":2344},[2345],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":2347},[2348,2349,2352],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":2350},[2351],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":2353},[2354],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":2356},[2357,2358,2361],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":2359},[2360],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":2362},[2363],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":2365},[2366,2367],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":2368},[2369],{"title":151,"path":152,"stem":153},1778094796156]