Skip to content

[a11y] InsightsRail tabs lack ARIA tablist/tab — migrate to Tabs primitive #91

@fedorovvvv

Description

@fedorovvvv

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions