Accessible Tabbed Workspace with Async Panels and Focus Management
Build an accessible, dynamic tabbed workspace component following WAI-ARIA tabbed interface guidelines, featuring keyboard navigation, automatic versus manual selection modes, asynchronous panel content loading with race condition handling, and dynamic tab management (add/close).
Requirements
-
ARIA Structure & Semantics
- Tab bar container must have
role="tablist"andaria-label="Workspace Tabs". - Each tab element must have
role="tab",id="tab-{id}",aria-controls="panel-{id}", andaria-selected="true"(active tab) or"false"(inactive tabs). - Only the active tab must be in the natural tab order (
tabIndex={0}); inactive tabs must havetabIndex={-1}. - Each tab panel must have
role="tabpanel",id="panel-{id}",aria-labelledby="tab-{id}", andtabIndex={0}.
- Tab bar container must have
-
Keyboard Navigation & Selection Modes
- A checkbox labeled
"Automatic activation"(aria-label="Automatic activation") toggles keyboard selection mode. Default is unchecked (Manual mode). - Navigating tabs with
ArrowRightandArrowLeftkeys loops around focus (first <-> last). Homekey moves focus to the first tab;Endkey moves focus to the last tab.- In Manual mode (default):
ArrowRight,ArrowLeft,Home, andEndmove focus between tabs without activating them. PressingEnterorSpaceon a focused tab activates it. - In Automatic mode:
ArrowRight,ArrowLeft,Home, andEndimmediately activate and select the newly focused tab.
- A checkbox labeled
-
Async Panel Loading & Race Condition Handling
- Selecting a tab fetches panel data from
/api/tabs/{id}(e.g./api/tabs/overview). - While fetching, the panel displays
"Loading panel content...". - If switching tabs rapidly, pending HTTP requests must be aborted or ignored so that only the response corresponding to the currently active tab is displayed.
- If the request fails, display error message
"Failed to load tab data"and a button labeled"Retry"(aria-label="Retry loading content").
- Selecting a tab fetches panel data from
-
Dynamic Tab Closing & Adding
- Each tab has a close button labeled
"Close {title} tab"(e.g."Close Overview tab"). - Clicking or pressing
Enter/Spaceon the close button removes that tab. - Closing the active tab transfers active selection to the adjacent tab (the next tab, or the previous tab if closing the last tab). Focus moves to the new active tab.
- If all tabs are closed, the tab panel area displays
"No open tabs". - An
"Add Tab"button (aria-label="Add tab") appends a new tab (e.g., ID"custom-1", Title"Tab 1", fetching/api/tabs/custom-1) and activates it immediately.
- Each tab has a close button labeled
The setup
The files open on a skeleton. Build the behaviour the requirements describe.
- src/main.jsx
- src/App.jsx
- src/styles.css
The preview has no network. fetch is answered by this problem's API:
- GET /api/tabs/overview
- GET /api/tabs/analytics
- GET /api/tabs/settings
- GET /api/tabs/custom-1
Follow-up: How would you persist open tabs and active selection state to localStorage while preserving scroll position in dynamic tab panels?
Must follow WAI-ARIA tab pattern guidelines strictly. Must handle fetch cancellations using AbortController or explicit request tracking. Styled using plain CSS.
- Views
- 1