Source: PRD-018 SC-2 audit — Frontend [W2], Security [W5].
template/src/widgets/insights-rail/ui/InsightsRail.svelte:88-108
Tabs are hand-rolled <nav class="tabs"> + <button class="tab" class:on={...}> with no ARIA tab semantics:
- No
role="tablist" on the wrapper.
- No
role="tab" / aria-selected on each tab button.
- No
aria-controls linking each tab to its panel.
Pre-migration this was the same — so this is preserved status quo, not a regression — but the shared/ui catalogue ships Tabs / TabsList / TabsTrigger (bits-ui-backed) which would deliver these for free. Per rule 24 + PRD-018 SC-2 spirit, swapping is the right move.
Fix
Replace the hand-rolled tabs with the catalogue primitive:
<TabsList aria-label="Insights">
{#each TABS as t (t.key)}
<TabsTrigger value={t.key}>
{t.label}
{#if t.badge() !== null && t.badge() !== 0}
<Badge variant="ghost" size="sm" tone="mono">{t.badge()}</Badge>
{/if}
</TabsTrigger>
{/each}
</TabsList>
Local state via $bindable already in place; bits-ui handles arrow-key navigation, roving tabindex, and announcements.
Acceptance
- DevTools Accessibility tree shows
tablist on the wrapper, tab on items, aria-selected="true" on the active tab.
- Keyboard: ←/→ moves between tabs, Tab exits the group.
- No visual regression vs current state.
Audit traceability
- Reviewers: Frontend, Security
Source: PRD-018 SC-2 audit — Frontend [W2], Security [W5].
template/src/widgets/insights-rail/ui/InsightsRail.svelte:88-108Tabs are hand-rolled
<nav class="tabs">+<button class="tab" class:on={...}>with no ARIA tab semantics:role="tablist"on the wrapper.role="tab"/aria-selectedon each tab button.aria-controlslinking each tab to its panel.Pre-migration this was the same — so this is preserved status quo, not a regression — but the
shared/uicatalogue shipsTabs/TabsList/TabsTrigger(bits-ui-backed) which would deliver these for free. Per rule 24 + PRD-018 SC-2 spirit, swapping is the right move.Fix
Replace the hand-rolled tabs with the catalogue primitive:
Local state via
$bindablealready in place; bits-ui handles arrow-key navigation, roving tabindex, and announcements.Acceptance
tabliston the wrapper,tabon items,aria-selected="true"on the active tab.Audit traceability