[{"data":1,"prerenderedAt":2659},["ShallowReactive",2],{"site-header-nav":3,"page-\u002Freact-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002Fbuilding-accessible-tabs-in-react-without-radix-ui\u002F":156,"content-navigation":2585},[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":85,"body":158,"date":2578,"description":2579,"extension":2580,"image":2578,"meta":2581,"modifiedAt":2578,"navigation":479,"noindex":2582,"path":86,"publishedAt":2578,"seo":2583,"stem":87,"updatedAt":2578,"__hash__":2584},"content\u002Freact-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002Fbuilding-accessible-tabs-in-react-without-radix-ui\u002Findex.md",{"type":159,"value":160,"toc":2560},"minimark",[161,165,202,207,210,215,282,286,589,613,617,628,632,667,671,1069,1088,1092,1095,1099,1140,1147,1706,1716,1720,2439,2443,2524,2528,2534,2543,2556],[162,163,85],"h1",{"id":164},"building-accessible-tabs-in-react-without-radix-ui",[166,167,168,169,176,177,181,182,185,186,189,190,193,194,197,198,201],"p",{},"This guide provides a step-by-step implementation for WAI-ARIA compliant tab components in React 18+, bypassing third-party abstractions while maintaining strict keyboard navigation, screen reader compatibility, and deterministic state synchronization. The architecture directly maps to ",[170,171,175],"a",{"href":172,"rel":173},"https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002Fquickref\u002F",[174],"nofollow","WCAG 2.1 Success Criteria",": ",[178,179,180],"code",{},"1.3.1 Info and Relationships"," (semantic role mapping), ",[178,183,184],{},"2.1.1 Keyboard"," (full arrow\u002Ftab traversal), ",[178,187,188],{},"2.4.3 Focus Order"," (logical DOM sequence), and ",[178,191,192],{},"4.1.2 Name, Role, Value"," (explicit ",[178,195,196],{},"aria-*"," attribute binding). Manual implementation aligns with established ",[170,199,76],{"href":200},"\u002Freact-nextjs-accessibility-patterns\u002F"," for engineering teams requiring deterministic control over focus management, render cycles, and bundle size.",[203,204,206],"h2",{"id":205},"core-aria-architecture-for-tabs","Core ARIA Architecture for Tabs",[166,208,209],{},"The accessibility tree must explicitly declare the tab widget structure. Screen readers rely on role inheritance and ID cross-referencing to parse tab relationships correctly.",[211,212,214],"h3",{"id":213},"structural-requirements","Structural Requirements",[216,217,218,232,252,267],"ul",{},[219,220,221,176,225,228,229],"li",{},[222,223,224],"strong",{},"Container",[178,226,227],{},"role=\"tablist\""," with ",[178,230,231],{},"aria-orientation=\"horizontal\"",[219,233,234,176,237,240,241,244,245,248,249],{},[222,235,236],{},"Triggers",[178,238,239],{},"\u003Cbutton>"," elements with ",[178,242,243],{},"role=\"tab\"",", ",[178,246,247],{},"aria-selected",", and ",[178,250,251],{},"aria-controls",[219,253,254,176,257,228,260,263,264],{},[222,255,256],{},"Panels",[178,258,259],{},"role=\"tabpanel\"",[178,261,262],{},"aria-labelledby"," referencing the corresponding tab ",[178,265,266],{},"id",[219,268,269,272,273,244,276,244,278,281],{},[222,270,271],{},"Constraint",": Never nest interactive elements (",[178,274,275],{},"\u003Ca>",[178,277,239],{},[178,279,280],{},"\u003Cinput>",") inside tab triggers. This breaks native activation semantics and creates nested focus traps.",[211,283,285],{"id":284},"base-jsx-structure","Base JSX Structure",[287,288,293],"pre",{"className":289,"code":290,"language":291,"meta":292,"style":292},"language-tsx shiki shiki-themes github-light github-dark","\u003Cdiv role=\"tablist\" aria-orientation=\"horizontal\">\n \u003Cbutton role=\"tab\" aria-selected={true} aria-controls=\"panel-1\" id=\"tab-1\">\n Tab 1\n \u003C\u002Fbutton>\n \u003Cbutton role=\"tab\" aria-selected={false} aria-controls=\"panel-2\" id=\"tab-2\" tabIndex={-1}>\n Tab 2\n \u003C\u002Fbutton>\n\u003C\u002Fdiv>\n\n\u003Cdiv role=\"tabpanel\" id=\"panel-1\" aria-labelledby=\"tab-1\" tabIndex={0}>\n Panel 1 Content\n\u003C\u002Fdiv>\n\u003Cdiv role=\"tabpanel\" id=\"panel-2\" aria-labelledby=\"tab-2\" tabIndex={0} hidden>\n Panel 2 Content\n\u003C\u002Fdiv>\n","tsx","",[178,294,295,331,379,385,395,449,455,464,474,481,519,525,534,574,580],{"__ignoreMap":292},[296,297,300,304,308,312,316,320,323,325,328],"span",{"class":298,"line":299},"line",1,[296,301,303],{"class":302},"sVt8B","\u003C",[296,305,307],{"class":306},"s9eBZ","div",[296,309,311],{"class":310},"sScJk"," role",[296,313,315],{"class":314},"szBVR","=",[296,317,319],{"class":318},"sZZnC","\"tablist\"",[296,321,322],{"class":310}," aria-orientation",[296,324,315],{"class":314},[296,326,327],{"class":318},"\"horizontal\"",[296,329,330],{"class":302},">\n",[296,332,334,337,340,342,344,347,350,352,355,359,362,364,366,369,372,374,377],{"class":298,"line":333},2,[296,335,336],{"class":302}," \u003C",[296,338,339],{"class":306},"button",[296,341,311],{"class":310},[296,343,315],{"class":314},[296,345,346],{"class":318},"\"tab\"",[296,348,349],{"class":310}," aria-selected",[296,351,315],{"class":314},[296,353,354],{"class":302},"{",[296,356,358],{"class":357},"sj4cs","true",[296,360,361],{"class":302},"} ",[296,363,251],{"class":310},[296,365,315],{"class":314},[296,367,368],{"class":318},"\"panel-1\"",[296,370,371],{"class":310}," id",[296,373,315],{"class":314},[296,375,376],{"class":318},"\"tab-1\"",[296,378,330],{"class":302},[296,380,382],{"class":298,"line":381},3,[296,383,384],{"class":302}," Tab 1\n",[296,386,388,391,393],{"class":298,"line":387},4,[296,389,390],{"class":302}," \u003C\u002F",[296,392,339],{"class":306},[296,394,330],{"class":302},[296,396,398,400,402,404,406,408,410,412,414,417,419,421,423,426,428,430,433,436,438,440,443,446],{"class":298,"line":397},5,[296,399,336],{"class":302},[296,401,339],{"class":306},[296,403,311],{"class":310},[296,405,315],{"class":314},[296,407,346],{"class":318},[296,409,349],{"class":310},[296,411,315],{"class":314},[296,413,354],{"class":302},[296,415,416],{"class":357},"false",[296,418,361],{"class":302},[296,420,251],{"class":310},[296,422,315],{"class":314},[296,424,425],{"class":318},"\"panel-2\"",[296,427,371],{"class":310},[296,429,315],{"class":314},[296,431,432],{"class":318},"\"tab-2\"",[296,434,435],{"class":310}," tabIndex",[296,437,315],{"class":314},[296,439,354],{"class":302},[296,441,442],{"class":314},"-",[296,444,445],{"class":357},"1",[296,447,448],{"class":302},"}>\n",[296,450,452],{"class":298,"line":451},6,[296,453,454],{"class":302}," Tab 2\n",[296,456,458,460,462],{"class":298,"line":457},7,[296,459,390],{"class":302},[296,461,339],{"class":306},[296,463,330],{"class":302},[296,465,467,470,472],{"class":298,"line":466},8,[296,468,469],{"class":302},"\u003C\u002F",[296,471,307],{"class":306},[296,473,330],{"class":302},[296,475,477],{"class":298,"line":476},9,[296,478,480],{"emptyLinePlaceholder":479},true,"\n",[296,482,484,486,488,490,492,495,497,499,501,504,506,508,510,512,514,517],{"class":298,"line":483},10,[296,485,303],{"class":302},[296,487,307],{"class":306},[296,489,311],{"class":310},[296,491,315],{"class":314},[296,493,494],{"class":318},"\"tabpanel\"",[296,496,371],{"class":310},[296,498,315],{"class":314},[296,500,368],{"class":318},[296,502,503],{"class":310}," aria-labelledby",[296,505,315],{"class":314},[296,507,376],{"class":318},[296,509,435],{"class":310},[296,511,315],{"class":314},[296,513,354],{"class":302},[296,515,516],{"class":357},"0",[296,518,448],{"class":302},[296,520,522],{"class":298,"line":521},11,[296,523,524],{"class":302}," Panel 1 Content\n",[296,526,528,530,532],{"class":298,"line":527},12,[296,529,469],{"class":302},[296,531,307],{"class":306},[296,533,330],{"class":302},[296,535,537,539,541,543,545,547,549,551,553,555,557,559,561,563,565,567,569,572],{"class":298,"line":536},13,[296,538,303],{"class":302},[296,540,307],{"class":306},[296,542,311],{"class":310},[296,544,315],{"class":314},[296,546,494],{"class":318},[296,548,371],{"class":310},[296,550,315],{"class":314},[296,552,425],{"class":318},[296,554,503],{"class":310},[296,556,315],{"class":314},[296,558,432],{"class":318},[296,560,435],{"class":310},[296,562,315],{"class":314},[296,564,354],{"class":302},[296,566,516],{"class":357},[296,568,361],{"class":302},[296,570,571],{"class":310},"hidden",[296,573,330],{"class":302},[296,575,577],{"class":298,"line":576},14,[296,578,579],{"class":302}," Panel 2 Content\n",[296,581,583,585,587],{"class":298,"line":582},15,[296,584,469],{"class":302},[296,586,307],{"class":306},[296,588,330],{"class":302},[166,590,591,594,595,598,599,601,602,604,605,608,609,612],{},[222,592,593],{},"Debugging Workflow",": Run ",[178,596,597],{},"axe-core"," via browser extension or CLI. Verify that ",[178,600,251],{}," values exactly match panel ",[178,603,266],{}," attributes. In VoiceOver\u002FNVDA, navigate to the tablist and confirm the screen reader announces ",[178,606,607],{},"\"Tab 1 of 3\""," and reads the selected state correctly. When evaluating whether to build custom or integrate ",[170,610,79],{"href":611},"\u002Freact-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002F",", validate that your baseline architecture passes these role-exposure checks before adding business logic.",[203,614,616],{"id":615},"keyboard-navigation-focus-management","Keyboard Navigation & Focus Management",[166,618,619,620,623,624,627],{},"Tab traversal must follow the WAI-ARIA Authoring Practices for manual activation mode. Focus remains within the ",[178,621,622],{},"tablist"," until the user explicitly presses ",[178,625,626],{},"Tab"," to exit.",[211,629,631],{"id":630},"event-handling-logic","Event Handling Logic",[216,633,634,644,653,662],{},[219,635,636,639,640,643],{},[178,637,638],{},"ArrowRight"," \u002F ",[178,641,642],{},"ArrowLeft",": Cycles focus through tabs in reading order. Wraps at boundaries.",[219,645,646,639,649,652],{},[178,647,648],{},"Home",[178,650,651],{},"End",": Jumps focus to the first or last tab.",[219,654,655,639,658,661],{},[178,656,657],{},"Enter",[178,659,660],{},"Space",": Activates the focused tab (manual activation).",[219,663,664,666],{},[178,665,626],{},": Exits the tablist and moves focus to the next focusable element in the document flow.",[211,668,670],{"id":669},"implementation","Implementation",[287,672,674],{"className":289,"code":673,"language":291,"meta":292,"style":292},"const handleKeyDown = (event: React.KeyboardEvent\u003CHTMLDivElement>) => {\n const { key } = event;\n const currentIndex = activeIndex;\n const tabCount = tabs.length;\n\n if (key === 'ArrowRight') {\n event.preventDefault();\n const nextIndex = (currentIndex + 1) % tabCount;\n setActiveIndex(nextIndex);\n focusTab(nextIndex);\n } else if (key === 'ArrowLeft') {\n event.preventDefault();\n const prevIndex = (currentIndex - 1 + tabCount) % tabCount;\n setActiveIndex(prevIndex);\n focusTab(prevIndex);\n } else if (key === 'Home') {\n event.preventDefault();\n setActiveIndex(0);\n focusTab(0);\n } else if (key === 'End') {\n event.preventDefault();\n setActiveIndex(tabCount - 1);\n focusTab(tabCount - 1);\n }\n};\n\nconst focusTab = (index: number) => {\n tabRefs.current[index]?.focus();\n};\n",[178,675,676,720,739,751,769,773,790,801,828,836,843,861,869,894,901,907,925,934,947,958,976,985,999,1012,1018,1024,1029,1053,1064],{"__ignoreMap":292},[296,677,678,681,684,687,690,694,697,700,703,706,708,711,714,717],{"class":298,"line":299},[296,679,680],{"class":314},"const",[296,682,683],{"class":310}," handleKeyDown",[296,685,686],{"class":314}," =",[296,688,689],{"class":302}," (",[296,691,693],{"class":692},"s4XuR","event",[296,695,696],{"class":314},":",[296,698,699],{"class":310}," React",[296,701,702],{"class":302},".",[296,704,705],{"class":310},"KeyboardEvent",[296,707,303],{"class":302},[296,709,710],{"class":310},"HTMLDivElement",[296,712,713],{"class":302},">) ",[296,715,716],{"class":314},"=>",[296,718,719],{"class":302}," {\n",[296,721,722,725,728,731,734,736],{"class":298,"line":333},[296,723,724],{"class":314}," const",[296,726,727],{"class":302}," { ",[296,729,730],{"class":357},"key",[296,732,733],{"class":302}," } ",[296,735,315],{"class":314},[296,737,738],{"class":302}," event;\n",[296,740,741,743,746,748],{"class":298,"line":381},[296,742,724],{"class":314},[296,744,745],{"class":357}," currentIndex",[296,747,686],{"class":314},[296,749,750],{"class":302}," activeIndex;\n",[296,752,753,755,758,760,763,766],{"class":298,"line":387},[296,754,724],{"class":314},[296,756,757],{"class":357}," tabCount",[296,759,686],{"class":314},[296,761,762],{"class":302}," tabs.",[296,764,765],{"class":357},"length",[296,767,768],{"class":302},";\n",[296,770,771],{"class":298,"line":397},[296,772,480],{"emptyLinePlaceholder":479},[296,774,775,778,781,784,787],{"class":298,"line":451},[296,776,777],{"class":314}," if",[296,779,780],{"class":302}," (key ",[296,782,783],{"class":314},"===",[296,785,786],{"class":318}," 'ArrowRight'",[296,788,789],{"class":302},") {\n",[296,791,792,795,798],{"class":298,"line":457},[296,793,794],{"class":302}," event.",[296,796,797],{"class":310},"preventDefault",[296,799,800],{"class":302},"();\n",[296,802,803,805,808,810,813,816,819,822,825],{"class":298,"line":466},[296,804,724],{"class":314},[296,806,807],{"class":357}," nextIndex",[296,809,686],{"class":314},[296,811,812],{"class":302}," (currentIndex ",[296,814,815],{"class":314},"+",[296,817,818],{"class":357}," 1",[296,820,821],{"class":302},") ",[296,823,824],{"class":314},"%",[296,826,827],{"class":302}," tabCount;\n",[296,829,830,833],{"class":298,"line":476},[296,831,832],{"class":310}," setActiveIndex",[296,834,835],{"class":302},"(nextIndex);\n",[296,837,838,841],{"class":298,"line":483},[296,839,840],{"class":310}," focusTab",[296,842,835],{"class":302},[296,844,845,847,850,852,854,856,859],{"class":298,"line":521},[296,846,733],{"class":302},[296,848,849],{"class":314},"else",[296,851,777],{"class":314},[296,853,780],{"class":302},[296,855,783],{"class":314},[296,857,858],{"class":318}," 'ArrowLeft'",[296,860,789],{"class":302},[296,862,863,865,867],{"class":298,"line":527},[296,864,794],{"class":302},[296,866,797],{"class":310},[296,868,800],{"class":302},[296,870,871,873,876,878,880,882,884,887,890,892],{"class":298,"line":536},[296,872,724],{"class":314},[296,874,875],{"class":357}," prevIndex",[296,877,686],{"class":314},[296,879,812],{"class":302},[296,881,442],{"class":314},[296,883,818],{"class":357},[296,885,886],{"class":314}," +",[296,888,889],{"class":302}," tabCount) ",[296,891,824],{"class":314},[296,893,827],{"class":302},[296,895,896,898],{"class":298,"line":576},[296,897,832],{"class":310},[296,899,900],{"class":302},"(prevIndex);\n",[296,902,903,905],{"class":298,"line":582},[296,904,840],{"class":310},[296,906,900],{"class":302},[296,908,910,912,914,916,918,920,923],{"class":298,"line":909},16,[296,911,733],{"class":302},[296,913,849],{"class":314},[296,915,777],{"class":314},[296,917,780],{"class":302},[296,919,783],{"class":314},[296,921,922],{"class":318}," 'Home'",[296,924,789],{"class":302},[296,926,928,930,932],{"class":298,"line":927},17,[296,929,794],{"class":302},[296,931,797],{"class":310},[296,933,800],{"class":302},[296,935,937,939,942,944],{"class":298,"line":936},18,[296,938,832],{"class":310},[296,940,941],{"class":302},"(",[296,943,516],{"class":357},[296,945,946],{"class":302},");\n",[296,948,950,952,954,956],{"class":298,"line":949},19,[296,951,840],{"class":310},[296,953,941],{"class":302},[296,955,516],{"class":357},[296,957,946],{"class":302},[296,959,961,963,965,967,969,971,974],{"class":298,"line":960},20,[296,962,733],{"class":302},[296,964,849],{"class":314},[296,966,777],{"class":314},[296,968,780],{"class":302},[296,970,783],{"class":314},[296,972,973],{"class":318}," 'End'",[296,975,789],{"class":302},[296,977,979,981,983],{"class":298,"line":978},21,[296,980,794],{"class":302},[296,982,797],{"class":310},[296,984,800],{"class":302},[296,986,988,990,993,995,997],{"class":298,"line":987},22,[296,989,832],{"class":310},[296,991,992],{"class":302},"(tabCount ",[296,994,442],{"class":314},[296,996,818],{"class":357},[296,998,946],{"class":302},[296,1000,1002,1004,1006,1008,1010],{"class":298,"line":1001},23,[296,1003,840],{"class":310},[296,1005,992],{"class":302},[296,1007,442],{"class":314},[296,1009,818],{"class":357},[296,1011,946],{"class":302},[296,1013,1015],{"class":298,"line":1014},24,[296,1016,1017],{"class":302}," }\n",[296,1019,1021],{"class":298,"line":1020},25,[296,1022,1023],{"class":302},"};\n",[296,1025,1027],{"class":298,"line":1026},26,[296,1028,480],{"emptyLinePlaceholder":479},[296,1030,1032,1034,1036,1038,1040,1042,1044,1047,1049,1051],{"class":298,"line":1031},27,[296,1033,680],{"class":314},[296,1035,840],{"class":310},[296,1037,686],{"class":314},[296,1039,689],{"class":302},[296,1041,69],{"class":692},[296,1043,696],{"class":314},[296,1045,1046],{"class":357}," number",[296,1048,821],{"class":302},[296,1050,716],{"class":314},[296,1052,719],{"class":302},[296,1054,1056,1059,1062],{"class":298,"line":1055},28,[296,1057,1058],{"class":302}," tabRefs.current[index]?.",[296,1060,1061],{"class":310},"focus",[296,1063,800],{"class":302},[296,1065,1067],{"class":298,"line":1066},29,[296,1068,1023],{"class":302},[166,1070,1071,1074,1075,68,1077,1079,1080,1083,1084,1087],{},[222,1072,1073],{},"Testing Configuration",": Disable mouse input during QA. Verify that ",[178,1076,638],{},[178,1078,642],{}," moves focus without triggering panel content re-renders prematurely. Confirm the browser's native focus ring remains visible and meets ",[178,1081,1082],{},"WCAG 2.4.7 Focus Visible"," contrast ratios. Use Playwright's ",[178,1085,1086],{},"page.keyboard.press()"," to automate traversal validation in CI.",[203,1089,1091],{"id":1090},"state-synchronization-screen-reader-announcements","State Synchronization & Screen Reader Announcements",[166,1093,1094],{},"React's reconciliation cycle can desynchronize ARIA attributes if state updates are not tightly coupled to DOM visibility. Inactive panels must be removed from the accessibility tree to prevent screen readers from traversing hidden content.",[211,1096,1098],{"id":1097},"state-visibility-strategy","State & Visibility Strategy",[216,1100,1101,1119,1137],{},[219,1102,1103,1104,1107,1108,1111,1112,1114,1115,1118],{},"Track ",[178,1105,1106],{},"activeIndex"," via ",[178,1109,1110],{},"useState",". Derive ",[178,1113,247],{}," and ",[178,1116,1117],{},"tabIndex"," from this single source of truth.",[219,1120,1121,1122,1124,1125,1128,1129,1132,1133,1136],{},"Hide inactive panels using the native ",[178,1123,571],{}," attribute or ",[178,1126,1127],{},"aria-hidden=\"true\""," combined with ",[178,1130,1131],{},"display: none",". Avoid ",[178,1134,1135],{},"visibility: hidden"," as it preserves layout space and can leak focus.",[219,1138,1139],{},"Implement a custom hook to encapsulate prop generation, ensuring consistent ARIA mapping across component instances.",[211,1141,1143,1146],{"id":1142},"useaccessibletabs-hook",[178,1144,1145],{},"useAccessibleTabs"," Hook",[287,1148,1150],{"className":289,"code":1149,"language":291,"meta":292,"style":292},"import { useState, useCallback, useRef } from 'react';\n\nexport function useAccessibleTabs(tabCount: number) {\n const [activeIndex, setActiveIndex] = useState(0);\n const tabRefs = useRef\u003C(HTMLButtonElement | null)[]>([]);\n\n const getTablistProps = useCallback(() => ({\n role: 'tablist' as const,\n 'aria-orientation': 'horizontal' as const,\n }), []);\n\n const getTabProps = useCallback((index: number) => ({\n role: 'tab' as const,\n id: `tab-${index}`,\n 'aria-controls': `panel-${index}`,\n 'aria-selected': index === activeIndex,\n tabIndex: index === activeIndex ? 0 : -1,\n ref: (el: HTMLButtonElement | null) => { tabRefs.current[index] = el; },\n onClick: () => setActiveIndex(index),\n onKeyDown: (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setActiveIndex(index);\n }\n },\n }), [activeIndex]);\n\n const getPanelProps = useCallback((index: number) => ({\n role: 'tabpanel' as const,\n id: `panel-${index}`,\n 'aria-labelledby': `tab-${index}`,\n tabIndex: 0,\n hidden: index !== activeIndex,\n }), [activeIndex]);\n\n return {\n activeIndex,\n setActiveIndex,\n tabRefs,\n getTablistProps,\n getTabProps,\n getPanelProps,\n };\n}\n",[178,1151,1152,1168,1172,1194,1222,1249,1253,1273,1289,1305,1310,1314,1340,1353,1368,1384,1397,1423,1455,1470,1494,1519,1528,1535,1539,1544,1549,1553,1578,1591,1604,1620,1630,1641,1646,1651,1659,1664,1670,1676,1682,1688,1694,1700],{"__ignoreMap":292},[296,1153,1154,1157,1160,1163,1166],{"class":298,"line":299},[296,1155,1156],{"class":314},"import",[296,1158,1159],{"class":302}," { useState, useCallback, useRef } ",[296,1161,1162],{"class":314},"from",[296,1164,1165],{"class":318}," 'react'",[296,1167,768],{"class":302},[296,1169,1170],{"class":298,"line":333},[296,1171,480],{"emptyLinePlaceholder":479},[296,1173,1174,1177,1180,1183,1185,1188,1190,1192],{"class":298,"line":381},[296,1175,1176],{"class":314},"export",[296,1178,1179],{"class":314}," function",[296,1181,1182],{"class":310}," useAccessibleTabs",[296,1184,941],{"class":302},[296,1186,1187],{"class":692},"tabCount",[296,1189,696],{"class":314},[296,1191,1046],{"class":357},[296,1193,789],{"class":302},[296,1195,1196,1198,1201,1203,1205,1208,1211,1213,1216,1218,1220],{"class":298,"line":387},[296,1197,724],{"class":314},[296,1199,1200],{"class":302}," [",[296,1202,1106],{"class":357},[296,1204,244],{"class":302},[296,1206,1207],{"class":357},"setActiveIndex",[296,1209,1210],{"class":302},"] ",[296,1212,315],{"class":314},[296,1214,1215],{"class":310}," useState",[296,1217,941],{"class":302},[296,1219,516],{"class":357},[296,1221,946],{"class":302},[296,1223,1224,1226,1229,1231,1234,1237,1240,1243,1246],{"class":298,"line":397},[296,1225,724],{"class":314},[296,1227,1228],{"class":357}," tabRefs",[296,1230,686],{"class":314},[296,1232,1233],{"class":310}," useRef",[296,1235,1236],{"class":302},"\u003C(",[296,1238,1239],{"class":310},"HTMLButtonElement",[296,1241,1242],{"class":314}," |",[296,1244,1245],{"class":357}," null",[296,1247,1248],{"class":302},")[]>([]);\n",[296,1250,1251],{"class":298,"line":451},[296,1252,480],{"emptyLinePlaceholder":479},[296,1254,1255,1257,1260,1262,1265,1268,1270],{"class":298,"line":457},[296,1256,724],{"class":314},[296,1258,1259],{"class":357}," getTablistProps",[296,1261,686],{"class":314},[296,1263,1264],{"class":310}," useCallback",[296,1266,1267],{"class":302},"(() ",[296,1269,716],{"class":314},[296,1271,1272],{"class":302}," ({\n",[296,1274,1275,1278,1281,1284,1286],{"class":298,"line":466},[296,1276,1277],{"class":302}," role: ",[296,1279,1280],{"class":318},"'tablist'",[296,1282,1283],{"class":314}," as",[296,1285,724],{"class":314},[296,1287,1288],{"class":302},",\n",[296,1290,1291,1294,1296,1299,1301,1303],{"class":298,"line":476},[296,1292,1293],{"class":318}," 'aria-orientation'",[296,1295,176],{"class":302},[296,1297,1298],{"class":318},"'horizontal'",[296,1300,1283],{"class":314},[296,1302,724],{"class":314},[296,1304,1288],{"class":302},[296,1306,1307],{"class":298,"line":483},[296,1308,1309],{"class":302}," }), []);\n",[296,1311,1312],{"class":298,"line":521},[296,1313,480],{"emptyLinePlaceholder":479},[296,1315,1316,1318,1321,1323,1325,1328,1330,1332,1334,1336,1338],{"class":298,"line":527},[296,1317,724],{"class":314},[296,1319,1320],{"class":357}," getTabProps",[296,1322,686],{"class":314},[296,1324,1264],{"class":310},[296,1326,1327],{"class":302},"((",[296,1329,69],{"class":692},[296,1331,696],{"class":314},[296,1333,1046],{"class":357},[296,1335,821],{"class":302},[296,1337,716],{"class":314},[296,1339,1272],{"class":302},[296,1341,1342,1344,1347,1349,1351],{"class":298,"line":536},[296,1343,1277],{"class":302},[296,1345,1346],{"class":318},"'tab'",[296,1348,1283],{"class":314},[296,1350,724],{"class":314},[296,1352,1288],{"class":302},[296,1354,1355,1358,1361,1363,1366],{"class":298,"line":576},[296,1356,1357],{"class":302}," id: ",[296,1359,1360],{"class":318},"`tab-${",[296,1362,69],{"class":302},[296,1364,1365],{"class":318},"}`",[296,1367,1288],{"class":302},[296,1369,1370,1373,1375,1378,1380,1382],{"class":298,"line":582},[296,1371,1372],{"class":318}," 'aria-controls'",[296,1374,176],{"class":302},[296,1376,1377],{"class":318},"`panel-${",[296,1379,69],{"class":302},[296,1381,1365],{"class":318},[296,1383,1288],{"class":302},[296,1385,1386,1389,1392,1394],{"class":298,"line":909},[296,1387,1388],{"class":318}," 'aria-selected'",[296,1390,1391],{"class":302},": index ",[296,1393,783],{"class":314},[296,1395,1396],{"class":302}," activeIndex,\n",[296,1398,1399,1402,1404,1407,1410,1413,1416,1419,1421],{"class":298,"line":927},[296,1400,1401],{"class":302}," tabIndex: index ",[296,1403,783],{"class":314},[296,1405,1406],{"class":302}," activeIndex ",[296,1408,1409],{"class":314},"?",[296,1411,1412],{"class":357}," 0",[296,1414,1415],{"class":314}," :",[296,1417,1418],{"class":314}," -",[296,1420,445],{"class":357},[296,1422,1288],{"class":302},[296,1424,1425,1428,1431,1434,1436,1439,1441,1443,1445,1447,1450,1452],{"class":298,"line":936},[296,1426,1427],{"class":310}," ref",[296,1429,1430],{"class":302},": (",[296,1432,1433],{"class":692},"el",[296,1435,696],{"class":314},[296,1437,1438],{"class":310}," HTMLButtonElement",[296,1440,1242],{"class":314},[296,1442,1245],{"class":357},[296,1444,821],{"class":302},[296,1446,716],{"class":314},[296,1448,1449],{"class":302}," { tabRefs.current[index] ",[296,1451,315],{"class":314},[296,1453,1454],{"class":302}," el; },\n",[296,1456,1457,1460,1463,1465,1467],{"class":298,"line":949},[296,1458,1459],{"class":310}," onClick",[296,1461,1462],{"class":302},": () ",[296,1464,716],{"class":314},[296,1466,832],{"class":310},[296,1468,1469],{"class":302},"(index),\n",[296,1471,1472,1475,1477,1480,1482,1484,1486,1488,1490,1492],{"class":298,"line":960},[296,1473,1474],{"class":310}," onKeyDown",[296,1476,1430],{"class":302},[296,1478,1479],{"class":692},"e",[296,1481,696],{"class":314},[296,1483,699],{"class":310},[296,1485,702],{"class":302},[296,1487,705],{"class":310},[296,1489,821],{"class":302},[296,1491,716],{"class":314},[296,1493,719],{"class":302},[296,1495,1496,1498,1501,1503,1506,1509,1512,1514,1517],{"class":298,"line":978},[296,1497,777],{"class":314},[296,1499,1500],{"class":302}," (e.key ",[296,1502,783],{"class":314},[296,1504,1505],{"class":318}," 'Enter'",[296,1507,1508],{"class":314}," ||",[296,1510,1511],{"class":302}," e.key ",[296,1513,783],{"class":314},[296,1515,1516],{"class":318}," ' '",[296,1518,789],{"class":302},[296,1520,1521,1524,1526],{"class":298,"line":987},[296,1522,1523],{"class":302}," e.",[296,1525,797],{"class":310},[296,1527,800],{"class":302},[296,1529,1530,1532],{"class":298,"line":1001},[296,1531,832],{"class":310},[296,1533,1534],{"class":302},"(index);\n",[296,1536,1537],{"class":298,"line":1014},[296,1538,1017],{"class":302},[296,1540,1541],{"class":298,"line":1020},[296,1542,1543],{"class":302}," },\n",[296,1545,1546],{"class":298,"line":1026},[296,1547,1548],{"class":302}," }), [activeIndex]);\n",[296,1550,1551],{"class":298,"line":1031},[296,1552,480],{"emptyLinePlaceholder":479},[296,1554,1555,1557,1560,1562,1564,1566,1568,1570,1572,1574,1576],{"class":298,"line":1055},[296,1556,724],{"class":314},[296,1558,1559],{"class":357}," getPanelProps",[296,1561,686],{"class":314},[296,1563,1264],{"class":310},[296,1565,1327],{"class":302},[296,1567,69],{"class":692},[296,1569,696],{"class":314},[296,1571,1046],{"class":357},[296,1573,821],{"class":302},[296,1575,716],{"class":314},[296,1577,1272],{"class":302},[296,1579,1580,1582,1585,1587,1589],{"class":298,"line":1066},[296,1581,1277],{"class":302},[296,1583,1584],{"class":318},"'tabpanel'",[296,1586,1283],{"class":314},[296,1588,724],{"class":314},[296,1590,1288],{"class":302},[296,1592,1594,1596,1598,1600,1602],{"class":298,"line":1593},30,[296,1595,1357],{"class":302},[296,1597,1377],{"class":318},[296,1599,69],{"class":302},[296,1601,1365],{"class":318},[296,1603,1288],{"class":302},[296,1605,1607,1610,1612,1614,1616,1618],{"class":298,"line":1606},31,[296,1608,1609],{"class":318}," 'aria-labelledby'",[296,1611,176],{"class":302},[296,1613,1360],{"class":318},[296,1615,69],{"class":302},[296,1617,1365],{"class":318},[296,1619,1288],{"class":302},[296,1621,1623,1626,1628],{"class":298,"line":1622},32,[296,1624,1625],{"class":302}," tabIndex: ",[296,1627,516],{"class":357},[296,1629,1288],{"class":302},[296,1631,1633,1636,1639],{"class":298,"line":1632},33,[296,1634,1635],{"class":302}," hidden: index ",[296,1637,1638],{"class":314},"!==",[296,1640,1396],{"class":302},[296,1642,1644],{"class":298,"line":1643},34,[296,1645,1548],{"class":302},[296,1647,1649],{"class":298,"line":1648},35,[296,1650,480],{"emptyLinePlaceholder":479},[296,1652,1654,1657],{"class":298,"line":1653},36,[296,1655,1656],{"class":314}," return",[296,1658,719],{"class":302},[296,1660,1662],{"class":298,"line":1661},37,[296,1663,1396],{"class":302},[296,1665,1667],{"class":298,"line":1666},38,[296,1668,1669],{"class":302}," setActiveIndex,\n",[296,1671,1673],{"class":298,"line":1672},39,[296,1674,1675],{"class":302}," tabRefs,\n",[296,1677,1679],{"class":298,"line":1678},40,[296,1680,1681],{"class":302}," getTablistProps,\n",[296,1683,1685],{"class":298,"line":1684},41,[296,1686,1687],{"class":302}," getTabProps,\n",[296,1689,1691],{"class":298,"line":1690},42,[296,1692,1693],{"class":302}," getPanelProps,\n",[296,1695,1697],{"class":298,"line":1696},43,[296,1698,1699],{"class":302}," };\n",[296,1701,1703],{"class":298,"line":1702},44,[296,1704,1705],{"class":302},"}\n",[166,1707,1708,1711,1712,1715],{},[222,1709,1710],{},"Testing Note",": Inspect the accessibility tree in Chrome DevTools. Confirm that only the active panel exists in the tree. Rapidly press arrow keys and verify screen readers do not queue overlapping announcements. If dynamic content loads inside panels, wrap the panel content in an ",[178,1713,1714],{},"aria-live=\"polite\""," region and debounce state updates to prevent speech queue overload.",[203,1717,1719],{"id":1718},"accessibletabs-component-implementation","AccessibleTabs Component Implementation",[287,1721,1723],{"className":289,"code":1722,"language":291,"meta":292,"style":292},"'use client';\n\nimport React from 'react';\nimport { useAccessibleTabs } from '.\u002FuseAccessibleTabs';\n\ninterface TabData {\n label: string;\n content: React.ReactNode;\n}\n\ninterface AccessibleTabsProps {\n tabs: TabData[];\n}\n\nexport function AccessibleTabs({ tabs }: AccessibleTabsProps) {\n const {\n activeIndex,\n setActiveIndex,\n tabRefs,\n getTablistProps,\n getTabProps,\n getPanelProps,\n } = useAccessibleTabs(tabs.length);\n\n const handleKeyDown = (e: React.KeyboardEvent\u003CHTMLDivElement>) => {\n const { key } = e;\n const count = tabs.length;\n\n if (key === 'ArrowRight') {\n e.preventDefault();\n const next = (activeIndex + 1) % count;\n setActiveIndex(next);\n tabRefs.current[next]?.focus();\n } else if (key === 'ArrowLeft') {\n e.preventDefault();\n const prev = (activeIndex - 1 + count) % count;\n setActiveIndex(prev);\n tabRefs.current[prev]?.focus();\n } else if (key === 'Home') {\n e.preventDefault();\n setActiveIndex(0);\n tabRefs.current[0]?.focus();\n } else if (key === 'End') {\n e.preventDefault();\n setActiveIndex(count - 1);\n tabRefs.current[count - 1]?.focus();\n }\n };\n\n return (\n \u003Cdiv>\n \u003Cdiv {...getTablistProps()} onKeyDown={handleKeyDown}>\n {tabs.map((tab, index) => (\n \u003Cbutton key={index} {...getTabProps(index)}>\n {tab.label}\n \u003C\u002Fbutton>\n ))}\n \u003C\u002Fdiv>\n\n {tabs.map((tab, index) => (\n \u003Cdiv key={index} {...getPanelProps(index)}>\n {tab.content}\n \u003C\u002Fdiv>\n ))}\n \u003C\u002Fdiv>\n );\n}\n",[178,1724,1725,1732,1736,1749,1763,1767,1777,1789,1805,1809,1813,1822,1834,1838,1842,1866,1872,1879,1885,1891,1897,1903,1909,1924,1928,1958,1973,1988,1992,2004,2012,2035,2042,2051,2067,2075,2099,2106,2115,2131,2139,2149,2163,2179,2187,2201,2217,2222,2227,2232,2240,2249,2276,2300,2323,2329,2338,2344,2353,2358,2379,2399,2405,2414,2419,2428,2434],{"__ignoreMap":292},[296,1726,1727,1730],{"class":298,"line":299},[296,1728,1729],{"class":318},"'use client'",[296,1731,768],{"class":302},[296,1733,1734],{"class":298,"line":333},[296,1735,480],{"emptyLinePlaceholder":479},[296,1737,1738,1740,1743,1745,1747],{"class":298,"line":381},[296,1739,1156],{"class":314},[296,1741,1742],{"class":302}," React ",[296,1744,1162],{"class":314},[296,1746,1165],{"class":318},[296,1748,768],{"class":302},[296,1750,1751,1753,1756,1758,1761],{"class":298,"line":387},[296,1752,1156],{"class":314},[296,1754,1755],{"class":302}," { useAccessibleTabs } ",[296,1757,1162],{"class":314},[296,1759,1760],{"class":318}," '.\u002FuseAccessibleTabs'",[296,1762,768],{"class":302},[296,1764,1765],{"class":298,"line":397},[296,1766,480],{"emptyLinePlaceholder":479},[296,1768,1769,1772,1775],{"class":298,"line":451},[296,1770,1771],{"class":314},"interface",[296,1773,1774],{"class":310}," TabData",[296,1776,719],{"class":302},[296,1778,1779,1782,1784,1787],{"class":298,"line":457},[296,1780,1781],{"class":692}," label",[296,1783,696],{"class":314},[296,1785,1786],{"class":357}," string",[296,1788,768],{"class":302},[296,1790,1791,1794,1796,1798,1800,1803],{"class":298,"line":466},[296,1792,1793],{"class":692}," content",[296,1795,696],{"class":314},[296,1797,699],{"class":310},[296,1799,702],{"class":302},[296,1801,1802],{"class":310},"ReactNode",[296,1804,768],{"class":302},[296,1806,1807],{"class":298,"line":476},[296,1808,1705],{"class":302},[296,1810,1811],{"class":298,"line":483},[296,1812,480],{"emptyLinePlaceholder":479},[296,1814,1815,1817,1820],{"class":298,"line":521},[296,1816,1771],{"class":314},[296,1818,1819],{"class":310}," AccessibleTabsProps",[296,1821,719],{"class":302},[296,1823,1824,1827,1829,1831],{"class":298,"line":527},[296,1825,1826],{"class":692}," tabs",[296,1828,696],{"class":314},[296,1830,1774],{"class":310},[296,1832,1833],{"class":302},"[];\n",[296,1835,1836],{"class":298,"line":536},[296,1837,1705],{"class":302},[296,1839,1840],{"class":298,"line":576},[296,1841,480],{"emptyLinePlaceholder":479},[296,1843,1844,1846,1848,1851,1854,1857,1860,1862,1864],{"class":298,"line":582},[296,1845,1176],{"class":314},[296,1847,1179],{"class":314},[296,1849,1850],{"class":310}," AccessibleTabs",[296,1852,1853],{"class":302},"({ ",[296,1855,1856],{"class":692},"tabs",[296,1858,1859],{"class":302}," }",[296,1861,696],{"class":314},[296,1863,1819],{"class":310},[296,1865,789],{"class":302},[296,1867,1868,1870],{"class":298,"line":909},[296,1869,724],{"class":314},[296,1871,719],{"class":302},[296,1873,1874,1877],{"class":298,"line":927},[296,1875,1876],{"class":357}," activeIndex",[296,1878,1288],{"class":302},[296,1880,1881,1883],{"class":298,"line":936},[296,1882,832],{"class":357},[296,1884,1288],{"class":302},[296,1886,1887,1889],{"class":298,"line":949},[296,1888,1228],{"class":357},[296,1890,1288],{"class":302},[296,1892,1893,1895],{"class":298,"line":960},[296,1894,1259],{"class":357},[296,1896,1288],{"class":302},[296,1898,1899,1901],{"class":298,"line":978},[296,1900,1320],{"class":357},[296,1902,1288],{"class":302},[296,1904,1905,1907],{"class":298,"line":987},[296,1906,1559],{"class":357},[296,1908,1288],{"class":302},[296,1910,1911,1913,1915,1917,1920,1922],{"class":298,"line":1001},[296,1912,733],{"class":302},[296,1914,315],{"class":314},[296,1916,1182],{"class":310},[296,1918,1919],{"class":302},"(tabs.",[296,1921,765],{"class":357},[296,1923,946],{"class":302},[296,1925,1926],{"class":298,"line":1014},[296,1927,480],{"emptyLinePlaceholder":479},[296,1929,1930,1932,1934,1936,1938,1940,1942,1944,1946,1948,1950,1952,1954,1956],{"class":298,"line":1020},[296,1931,724],{"class":314},[296,1933,683],{"class":310},[296,1935,686],{"class":314},[296,1937,689],{"class":302},[296,1939,1479],{"class":692},[296,1941,696],{"class":314},[296,1943,699],{"class":310},[296,1945,702],{"class":302},[296,1947,705],{"class":310},[296,1949,303],{"class":302},[296,1951,710],{"class":310},[296,1953,713],{"class":302},[296,1955,716],{"class":314},[296,1957,719],{"class":302},[296,1959,1960,1962,1964,1966,1968,1970],{"class":298,"line":1026},[296,1961,724],{"class":314},[296,1963,727],{"class":302},[296,1965,730],{"class":357},[296,1967,733],{"class":302},[296,1969,315],{"class":314},[296,1971,1972],{"class":302}," e;\n",[296,1974,1975,1977,1980,1982,1984,1986],{"class":298,"line":1031},[296,1976,724],{"class":314},[296,1978,1979],{"class":357}," count",[296,1981,686],{"class":314},[296,1983,762],{"class":302},[296,1985,765],{"class":357},[296,1987,768],{"class":302},[296,1989,1990],{"class":298,"line":1055},[296,1991,480],{"emptyLinePlaceholder":479},[296,1993,1994,1996,1998,2000,2002],{"class":298,"line":1066},[296,1995,777],{"class":314},[296,1997,780],{"class":302},[296,1999,783],{"class":314},[296,2001,786],{"class":318},[296,2003,789],{"class":302},[296,2005,2006,2008,2010],{"class":298,"line":1593},[296,2007,1523],{"class":302},[296,2009,797],{"class":310},[296,2011,800],{"class":302},[296,2013,2014,2016,2019,2021,2024,2026,2028,2030,2032],{"class":298,"line":1606},[296,2015,724],{"class":314},[296,2017,2018],{"class":357}," next",[296,2020,686],{"class":314},[296,2022,2023],{"class":302}," (activeIndex ",[296,2025,815],{"class":314},[296,2027,818],{"class":357},[296,2029,821],{"class":302},[296,2031,824],{"class":314},[296,2033,2034],{"class":302}," count;\n",[296,2036,2037,2039],{"class":298,"line":1622},[296,2038,832],{"class":310},[296,2040,2041],{"class":302},"(next);\n",[296,2043,2044,2047,2049],{"class":298,"line":1632},[296,2045,2046],{"class":302}," tabRefs.current[next]?.",[296,2048,1061],{"class":310},[296,2050,800],{"class":302},[296,2052,2053,2055,2057,2059,2061,2063,2065],{"class":298,"line":1643},[296,2054,733],{"class":302},[296,2056,849],{"class":314},[296,2058,777],{"class":314},[296,2060,780],{"class":302},[296,2062,783],{"class":314},[296,2064,858],{"class":318},[296,2066,789],{"class":302},[296,2068,2069,2071,2073],{"class":298,"line":1648},[296,2070,1523],{"class":302},[296,2072,797],{"class":310},[296,2074,800],{"class":302},[296,2076,2077,2079,2082,2084,2086,2088,2090,2092,2095,2097],{"class":298,"line":1653},[296,2078,724],{"class":314},[296,2080,2081],{"class":357}," prev",[296,2083,686],{"class":314},[296,2085,2023],{"class":302},[296,2087,442],{"class":314},[296,2089,818],{"class":357},[296,2091,886],{"class":314},[296,2093,2094],{"class":302}," count) ",[296,2096,824],{"class":314},[296,2098,2034],{"class":302},[296,2100,2101,2103],{"class":298,"line":1661},[296,2102,832],{"class":310},[296,2104,2105],{"class":302},"(prev);\n",[296,2107,2108,2111,2113],{"class":298,"line":1666},[296,2109,2110],{"class":302}," tabRefs.current[prev]?.",[296,2112,1061],{"class":310},[296,2114,800],{"class":302},[296,2116,2117,2119,2121,2123,2125,2127,2129],{"class":298,"line":1672},[296,2118,733],{"class":302},[296,2120,849],{"class":314},[296,2122,777],{"class":314},[296,2124,780],{"class":302},[296,2126,783],{"class":314},[296,2128,922],{"class":318},[296,2130,789],{"class":302},[296,2132,2133,2135,2137],{"class":298,"line":1678},[296,2134,1523],{"class":302},[296,2136,797],{"class":310},[296,2138,800],{"class":302},[296,2140,2141,2143,2145,2147],{"class":298,"line":1684},[296,2142,832],{"class":310},[296,2144,941],{"class":302},[296,2146,516],{"class":357},[296,2148,946],{"class":302},[296,2150,2151,2154,2156,2159,2161],{"class":298,"line":1690},[296,2152,2153],{"class":302}," tabRefs.current[",[296,2155,516],{"class":357},[296,2157,2158],{"class":302},"]?.",[296,2160,1061],{"class":310},[296,2162,800],{"class":302},[296,2164,2165,2167,2169,2171,2173,2175,2177],{"class":298,"line":1696},[296,2166,733],{"class":302},[296,2168,849],{"class":314},[296,2170,777],{"class":314},[296,2172,780],{"class":302},[296,2174,783],{"class":314},[296,2176,973],{"class":318},[296,2178,789],{"class":302},[296,2180,2181,2183,2185],{"class":298,"line":1702},[296,2182,1523],{"class":302},[296,2184,797],{"class":310},[296,2186,800],{"class":302},[296,2188,2190,2192,2195,2197,2199],{"class":298,"line":2189},45,[296,2191,832],{"class":310},[296,2193,2194],{"class":302},"(count ",[296,2196,442],{"class":314},[296,2198,818],{"class":357},[296,2200,946],{"class":302},[296,2202,2204,2207,2209,2211,2213,2215],{"class":298,"line":2203},46,[296,2205,2206],{"class":302}," tabRefs.current[count ",[296,2208,442],{"class":314},[296,2210,818],{"class":357},[296,2212,2158],{"class":302},[296,2214,1061],{"class":310},[296,2216,800],{"class":302},[296,2218,2220],{"class":298,"line":2219},47,[296,2221,1017],{"class":302},[296,2223,2225],{"class":298,"line":2224},48,[296,2226,1699],{"class":302},[296,2228,2230],{"class":298,"line":2229},49,[296,2231,480],{"emptyLinePlaceholder":479},[296,2233,2235,2237],{"class":298,"line":2234},50,[296,2236,1656],{"class":314},[296,2238,2239],{"class":302}," (\n",[296,2241,2243,2245,2247],{"class":298,"line":2242},51,[296,2244,336],{"class":302},[296,2246,307],{"class":306},[296,2248,330],{"class":302},[296,2250,2252,2254,2256,2259,2262,2265,2268,2271,2273],{"class":298,"line":2251},52,[296,2253,336],{"class":302},[296,2255,307],{"class":306},[296,2257,2258],{"class":302}," {",[296,2260,2261],{"class":314},"...",[296,2263,2264],{"class":310},"getTablistProps",[296,2266,2267],{"class":302},"()} ",[296,2269,2270],{"class":310},"onKeyDown",[296,2272,315],{"class":314},[296,2274,2275],{"class":302},"{handleKeyDown}>\n",[296,2277,2279,2282,2285,2287,2290,2292,2294,2296,2298],{"class":298,"line":2278},53,[296,2280,2281],{"class":302}," {tabs.",[296,2283,2284],{"class":310},"map",[296,2286,1327],{"class":302},[296,2288,2289],{"class":692},"tab",[296,2291,244],{"class":302},[296,2293,69],{"class":692},[296,2295,821],{"class":302},[296,2297,716],{"class":314},[296,2299,2239],{"class":302},[296,2301,2303,2305,2307,2310,2312,2315,2317,2320],{"class":298,"line":2302},54,[296,2304,336],{"class":302},[296,2306,339],{"class":306},[296,2308,2309],{"class":310}," key",[296,2311,315],{"class":314},[296,2313,2314],{"class":302},"{index} {",[296,2316,2261],{"class":314},[296,2318,2319],{"class":310},"getTabProps",[296,2321,2322],{"class":302},"(index)}>\n",[296,2324,2326],{"class":298,"line":2325},55,[296,2327,2328],{"class":302}," {tab.label}\n",[296,2330,2332,2334,2336],{"class":298,"line":2331},56,[296,2333,390],{"class":302},[296,2335,339],{"class":306},[296,2337,330],{"class":302},[296,2339,2341],{"class":298,"line":2340},57,[296,2342,2343],{"class":302}," ))}\n",[296,2345,2347,2349,2351],{"class":298,"line":2346},58,[296,2348,390],{"class":302},[296,2350,307],{"class":306},[296,2352,330],{"class":302},[296,2354,2356],{"class":298,"line":2355},59,[296,2357,480],{"emptyLinePlaceholder":479},[296,2359,2361,2363,2365,2367,2369,2371,2373,2375,2377],{"class":298,"line":2360},60,[296,2362,2281],{"class":302},[296,2364,2284],{"class":310},[296,2366,1327],{"class":302},[296,2368,2289],{"class":692},[296,2370,244],{"class":302},[296,2372,69],{"class":692},[296,2374,821],{"class":302},[296,2376,716],{"class":314},[296,2378,2239],{"class":302},[296,2380,2382,2384,2386,2388,2390,2392,2394,2397],{"class":298,"line":2381},61,[296,2383,336],{"class":302},[296,2385,307],{"class":306},[296,2387,2309],{"class":310},[296,2389,315],{"class":314},[296,2391,2314],{"class":302},[296,2393,2261],{"class":314},[296,2395,2396],{"class":310},"getPanelProps",[296,2398,2322],{"class":302},[296,2400,2402],{"class":298,"line":2401},62,[296,2403,2404],{"class":302}," {tab.content}\n",[296,2406,2408,2410,2412],{"class":298,"line":2407},63,[296,2409,390],{"class":302},[296,2411,307],{"class":306},[296,2413,330],{"class":302},[296,2415,2417],{"class":298,"line":2416},64,[296,2418,2343],{"class":302},[296,2420,2422,2424,2426],{"class":298,"line":2421},65,[296,2423,390],{"class":302},[296,2425,307],{"class":306},[296,2427,330],{"class":302},[296,2429,2431],{"class":298,"line":2430},66,[296,2432,2433],{"class":302}," );\n",[296,2435,2437],{"class":298,"line":2436},67,[296,2438,1705],{"class":302},[203,2440,2442],{"id":2441},"common-pitfalls","Common Pitfalls",[2444,2445,2446,2473,2485,2498,2512],"ol",{},[219,2447,2448,2459,2460,68,2462,2464,2465,68,2467,2470,2471,702],{},[222,2449,2450,2451,2454,2455,2458],{},"Using ",[178,2452,2453],{},"\u003Cdiv>"," or ",[178,2456,2457],{},"\u003Cspan>"," for tab triggers",": Breaks native keyboard activation (",[178,2461,657],{},[178,2463,660],{},") and requires manual ",[178,2466,1117],{},[178,2468,2469],{},"role"," patching. Always use ",[178,2472,239],{},[219,2474,2475,2481,2482,2484],{},[222,2476,2477,2478,2480],{},"Desynchronized ",[178,2479,247],{}," and visual state",": Causes screen readers to announce incorrect active tabs. Derive both from a single ",[178,2483,1106],{}," state variable.",[219,2486,2487,2492,2493,2454,2495,2497],{},[222,2488,2489,2490],{},"Hiding panels with ",[178,2491,1135],{},": Preserves layout and keeps elements in the accessibility tree. Use ",[178,2494,571],{},[178,2496,1131],{}," to fully remove them from the a11y tree.",[219,2499,2500,2503,2504,2507,2508,2511],{},[222,2501,2502],{},"Missing or low-contrast focus outlines",": Violates ",[178,2505,2506],{},"WCAG 2.4.7",". Implement explicit ",[178,2509,2510],{},":focus-visible"," styles with a minimum 3:1 contrast ratio against the background.",[219,2513,2514,2520,2521,2523],{},[222,2515,2516,2517],{},"Over-announcing with ",[178,2518,2519],{},"aria-live",": Rapid state changes flood screen reader queues. Debounce announcements or rely on native ",[178,2522,247],{}," state changes, which are announced automatically by modern assistive technology.",[203,2525,2527],{"id":2526},"frequently-asked-questions","Frequently Asked Questions",[166,2529,2530,2533],{},[222,2531,2532],{},"Should I use automatic or manual activation for React tabs?","\nManual activation (click\u002FEnter\u002FSpace) is the standard for performance and screen reader compatibility, particularly when panels contain heavy content, forms, or lazy-loaded data. Automatic activation requires aggressive debouncing and frequently causes unexpected viewport jumps or layout shifts.",[166,2535,2536,2539,2540,2542],{},[222,2537,2538],{},"How do I handle focus when a tab panel contains interactive elements?","\nFocus must remain on the tab button until the user explicitly presses ",[178,2541,626],{}," to move into the panel content. Do not auto-focus the first interactive element inside the panel unless explicitly mandated by UX. Auto-focusing disrupts standard keyboard navigation expectations and violates predictable focus order.",[166,2544,2545,2548,2549,2551,2552,2555],{},[222,2546,2547],{},"Can I use this pattern in Next.js App Router?","\nYes, but the component must be marked with ",[178,2550,1729],{},". Tabs require browser-side event listeners, ",[178,2553,2554],{},"useRef"," DOM access, and client-side state management. Server components cannot handle interactive tab logic or client-side focus management directly.",[2557,2558,2559],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":292,"searchDepth":333,"depth":333,"links":2561},[2562,2566,2570,2575,2576,2577],{"id":205,"depth":333,"text":206,"children":2563},[2564,2565],{"id":213,"depth":381,"text":214},{"id":284,"depth":381,"text":285},{"id":615,"depth":333,"text":616,"children":2567},[2568,2569],{"id":630,"depth":381,"text":631},{"id":669,"depth":381,"text":670},{"id":1090,"depth":333,"text":1091,"children":2571},[2572,2573],{"id":1097,"depth":381,"text":1098},{"id":1142,"depth":381,"text":2574},"useAccessibleTabs Hook",{"id":1718,"depth":333,"text":1719},{"id":2441,"depth":333,"text":2442},{"id":2526,"depth":333,"text":2527},null,"Build accessible React tabs without Radix UI using WAI-ARIA patterns, keyboard support, and state synchronization that stays robust.","md",{},false,{"title":85,"description":2579},"NEwrRMHilkk1RpqYr5sQ6J9EJCxoIecFQHVz1-7y_9E",[2586,2616,2617],{"title":5,"path":6,"stem":7,"children":2587},[2588,2589,2592,2595,2601,2607,2613],{"title":10,"path":6,"stem":11},{"title":13,"path":14,"stem":15,"children":2590},[2591],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":2593},[2594],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":2596},[2597,2598],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":2599},[2600],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":2602},[2603,2604],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":2605},[2606],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":2608},[2609,2610],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":2611},[2612],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":2614},[2615],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69},{"title":71,"path":72,"stem":73,"children":2618},[2619,2620,2626,2632,2635,2644,2653],{"title":76,"path":72,"stem":77},{"title":79,"path":80,"stem":81,"children":2621},[2622,2623],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":2624},[2625],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":2627},[2628,2629],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":2630},[2631],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":2633},[2634],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":2636},[2637,2638,2641],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":2639},[2640],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":2642},[2643],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":2645},[2646,2647,2650],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":2648},[2649],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":2651},[2652],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":2654},[2655,2656],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":2657},[2658],{"title":151,"path":152,"stem":153},1778094796371]