Tab Menu Horizontal

How the Tab Menu Horizontal works with a keyboard and assistive tech, and where it stands against WCAG 2.2 AA.

FreeReact and VueTab Menu18 blocksPro
AAWCAG 2.2 target
6/9contrast pairs passing
9keyboard interactions

The Tab Menu Horizontal is a WAI-ARIA tablist: one tab stop, the arrow keys move between tabs, and moving selects. The optional compact button opens every tab as a menu. It is held to WCAG 2.2 AA for contrast, focus and target size.

Keyboard#

Tab

Moves focus to the selected tab, then on to its panel. Only the selected tab is in the tab order.

Shift+Tab

Moves focus back from the panel to the selected tab.

→

Moves to the next tab and selects it, wrapping from last to first.

←

Moves to the previous tab and selects it, wrapping from first to last.

Home

Selects the first tab.

End

Selects the last tab.

Enter

On the compact button, opens the menu with the selected tab focused. In the menu, selects the focused tab and returns focus to it.

↓

In the menu, moves to the next tab, wrapping. Up moves back.

Esc

Closes the menu and returns focus to the compact button.

Semantics#

role="tablist"

With aria-orientation="horizontal". Give it a name with aria-label when the page has more than one tablist.

Always
role="tab"

A <button> with the tab role. Its badge and glyphs are inside the button, so a count is read with the label.

Each tab
aria-selected

true on the active tab only. The blue label and bar only echo it.

Each tab
aria-controls

Points at the tabpanel the strip switches. Pass panelId.

Each tab
role="tabpanel"

Labelled by the selected tab with aria-labelledby, and given tabindex="0" when it has nothing focusable inside.

Panel
tabindex="0" / "-1"

Only the selected tab is in the tab order.

Roving
aria-haspopup="menu"

With aria-expanded, and named More. The menu's items are menuitemradio, checked on the selected tab.

Compact button

Colour contrast#

Every variant and state has to meet WCAG 2.2 Level AA. Measure the colours as they land on screen: a translucent hover or press wash over whatever sits behind it.

4.5:1

Tab label · Default, Hover, Active

The 14px Medium label against the white strip in every state. The compact menu's options are measured the same way, with their 4% hover wash.

WCAG 1.4.3 Contrast (Minimum) ↗
3:1

Active bar · Active

The 2px (or 8px Thick) #005cf8 bar against the white strip and its #f2f2f2 hairline: 5.40:1 and 4.83:1.

WCAG 1.4.11 Non-text Contrast ↗
Not colour alone

Selected state · Active

The active tab is blue and also carries the bar, a shape the other tabs don't have, so selection doesn't rest on hue.

WCAG 1.4.1 Use of Color ↗
4.5:1

Count badge · With a count

White 12px numbers on #ec0d33 measure 4.49:1, just under 4.5:1.

WCAG 1.4.3 Contrast (Minimum) ↗
3:1

Focus indicator · Focus visible

A 2px #1a6ef8 ring with a white gap around the tab, 4.54:1 against white.

WCAG 1.4.11 Non-text Contrast ↗WCAG 2.4.13 Focus Appearance ↗

Measured contrast#

Text against the white strip or menu in each state, with the menu's hover wash laid over white; the count badge against its red fill. 3 of 9 pairs fall below their minimum (4.5:1 for text, 3:1 for graphics) and are marked in amber.

Part · elementDefaultHoverActive
Tab · label5.17passes14.35passes5.40passes
Compact menu · option5.17passes13.17passes5.40passes
Count badge · number4.49below the minimum4.49below the minimum4.49below the minimum

Motion#

  • The label colour changes over 150ms and at once under prefers-reduced-motion.
  • The bar moves to the new tab without sliding, and the compact button's chevron flips without a transition.

Checklist#

Wire each tab to its panel: pass panelId, and label the panel with aria-labelledby pointing at the selected tab's id rather than repeating the text in aria-label.

Selection follows focus. That suits panels that are already on the page; if a panel loads slowly, select on Enter or Space instead (manual activation), which the preview does not offer yet.

The count badge is 4.49:1, a hair under 4.5:1. Darken the red to #b30a26 or below before the code library ships.

Tabs are 20px tall with a 24px hit area, the WCAG 2.5.8 minimum; the 12px gap between them gives the spacing. The compact button is 24 × 24px.

Narrower than its tabs, the strip wraps onto a second row rather than hiding tabs off the edge. Keep it that way.

Use links in a <nav>, not tabs, when each label goes to a different page.