📋 Horizontal lists

Common mistake

A common mistake is placing horizontally scrollable content such as carousels or page views without providing an alternative navigation method for screen reader users. Screen readers use horizontal swipe gestures to move between elements, which directly conflicts with the swipe gesture used to scroll the list. As a result, users get stuck and cannot reach the list’s content at all.

🎯 Relevant elements

This guideline applies to any widget that scrolls horizontally and contains multiple navigable items:

  • Page views (PageView)
  • Horizontal list views (ListView with horizontal scroll direction)
  • Carousels and image sliders
  • Horizontally scrollable tab or chip rows

WCAG Guideline

This guideline is based on WCAG 2.2 — 2.1.1 Keyboard (Level A) and WCAG 2.2 — 2.5.1 Pointer Gestures (Level A). All functionality must be operable through a keyboard interface and must not depend on multipoint or path-based gestures alone.


Solution

Use the onIncrease and onDecrease callbacks within Flutter’s Semantics widget to provide an alternative navigation method. By wrapping a PageView with these callbacks, users can navigate pages by swiping up or down instead of horizontally. Also set liveRegion: true so that each new page’s content is automatically announced when it changes.

// ❌ Don't - horizontal swipe conflicts with screen reader navigation
PageView(
  children: pages,
);

// ✅ Do - provide up/down swipe as alternative navigation
int _currentPage = 0;
final PageController _controller = PageController();

Semantics(
  liveRegion: true,
  onIncrease: () {
    _controller.nextPage(duration: Duration(milliseconds: 300), curve: Curves.easeIn);
    setState(() => _currentPage++);
  },
  onDecrease: () {
    _controller.previousPage(duration: Duration(milliseconds: 300), curve: Curves.easeIn);
    setState(() => _currentPage--);
  },
  child: PageView(
    controller: _controller,
    children: pages,
  ),
);

Validation & Testing

Verify this guideline using one or more of the methods below. See the Validation & Testing setup guide for tool configuration.

SemanticsDebugger

Confirm that the PageView exposes increase/decrease actions in the semantic tree.

accessibility_tools screen reader mode

accessibility_tools

Activate screen reader mode and confirm the PageView exposes increase/decrease actions.

TalkBack & VoiceOver

Navigate through the horizontal list. Verify that all list content is reachable using up/down swipes and that new content is automatically announced after each navigation action.

flutter_test

Use flutter_test to assert that the semantic node exposes the increase and decrease actions.

testWidgets('PageView exposes increase and decrease actions', (tester) async {
  await tester.pumpWidget(MyWidget());

  expect(
    tester.getSemantics(find.byType(PageView)),
    matchesSemantics(
      hasIncreasedAction: true,
      hasDecreasedAction: true,
    ),
  );
});

🛠️ Usage baseflow-a11y-components library

This is enforced on the following components:

  • A11yHorizontalList

This site uses Just the Docs, a documentation theme for Jekyll.