Tab strip
canonControlled tablist that scrolls and fades when the tabs do not fit; no overflow menu, no disabled tab.
Used in 10 files · 7 areas · 10× — components/record, components/setkani, pages/ClenTymuPage, pages/MeetingDetailPage, pages/ProjectDetailPage, pages/TrashPage …
@acrm/ui <TabStrip>.acrm-tabs .acrm-tab .acrm-tabswrap .acrm-tabsedge +1SourceStoryLegacy card tabs.htmlKeyboard
- Tabs are real
<button role="tab">insiderole="tablist": Tab moves focus into the strip, Enter and Space switch. - Single tab stop (roving tabindex): Tab lands on the active tab. ←/→ move focus with wrap-around, Home/End jump to the ends; arrows do not switch — Enter or Space does. Activation is manual, because a panel loads its data when it mounts, so switching on every arrow would fire a request per keystroke. Arrows with Alt, Ctrl, Meta or Shift are left to the browser (NAV-3, 11 Sep 2026).
- The overflow chevrons are
aria-hiddenand only clickable when the edge is on — they are a pointer affordance, not a keyboard one. Keyboard users reach hidden tabs by tabbing, which scrolls them into view.
ARIA
- The strip is
role="tablist", each tabrole="tab"witharia-selected. - Optional
panelId: every tab getsid="<panelId>-tab-<key>"and the active tab getsaria-controls; the consumer marks the panelrole="tabpanel"witharia-labelledby. Only the active tab points at the panel, because only its panel is rendered. Wired in Trash and Project detail; not in Record tabs (the strip and the panels live in different components) or Meeting detail. - The count
.cntis plain text inside the button, so it is read as part of the tab's name ("Úkoly 3") — which is the intended reading. - Nothing announces that the content below changed after a switch.
Focus
.acrm-tabis a<button>withrole="tab", so the foundation ring fromds.cssapplies (outline: 2px solid var(--accent); outline-offset: 1px) — the selector list covers bothbuttonand[role='tab'], and the tabs CSS does not switch the outline off.- Focus follows the click; switching does not move focus elsewhere.
- A tab scrolled out of view is scrolled back by the browser when it takes focus.
Contrast
Computed from packages/ui/src/tokens/tokens.css:
- Inactive tab label: muted ink on the page surface — readable, but the active tab is the one carrying weight.
- Active tab: ink label plus the 2px accent underline (
--accenton--surface-100= 4.44:1 light, 3.83:1 dark) — the state is carried by both colour and the underline, not by colour alone. - Count
.cnt: muted pill; the number repeats in the accessible name, so it is not the only carrier. - Fade edges are decorative gradients over the background; they carry no information that the chevron does not.
Known limits
aria-controlsis not wired where the panel lives outside the strip's component.- No disabled state at all (neither prop nor CSS).
- The overflow chevrons are pointer-only.
- Switching a tab is silent for assistive technology.
Source: docs/tabs/tab-strip.a11y.md · GitLab