---
title: 'Cyclical Content Navigation'
source: 'https://academia.sh/en/courses/accessible-patterns/cyclical-content-navigation'
course: 'Accessible Component Patterns'
language: en
updated: '2026-08-19T05:19:51+00:00'
license: 'CC BY-SA 4.0'
---

# Cyclical Content Navigation

How the automatic transition relates to reading time, the unconditional requirement for a stop control, removing hidden cards from the tab order, and why automatically changing content is not announced.

In every pattern so far, content stayed in place and the user reached it at their own
pace. The featured-records strip on the catalog's home page does the opposite: five
cards are shown one at a time in a single area, and the transition happens without any
action from the user.

The pattern is usually justified by saving space — five cards occupy the room of one.
Its cost, though, is measurable: the relationship between the user's reading speed and
the transition interval, whether the motion can be stopped, and what hidden cards do in
the keyboard order. This lesson turns that cost into numbers.

## The Problem the Pattern Solves, and Where It Is Not Used

Cyclical navigation presents an ordered, equivalent set of content in a bounded space.
The condition for using it has two parts: the cards must be **alternatives** to one
another (missing one must not lose information), and the user must not be required to
see all of them.

If this condition is not met, the pattern is wrong. A sequence of steps, a form flow, or
a warning that must not be missed does not belong in a strip. The comparison in the last
section gives the reason as a number: at any given moment, eighty percent of the strip's
content is not visible.

## Reading Time versus the Transition Interval

The computation below estimates each card's reading time from its word count and
evaluates transition-interval options against that time.

```js
// cycle.mjs — an auto-advancing content strip: reading time, stopping, and tab order

// The "featured records" strip on the home page: five cards, one text per card.
const CARDS = [
  { title: "Astronomy Handbook", text: "Setting up the rig and keeping a measurement log.", actions: 2 },
  { title: "Northern Sky Atlas", text: "Seasonal maps of northern hemisphere constellations.", actions: 2 },
  { title: "Measurement Methods", text: "Calculating margin of error in temperature and humidity.", actions: 1 },
  { title: "Slope Meteorology", text: "Long-term records of temperature change by elevation.", actions: 2 },
  { title: "History of the Logbook", text: "Old archive formats and reading problems.", actions: 1 },
];

// 1. Reading time: estimated from word count.
// Measure: 200 words per minute (adult, familiar content), about 6 characters per word.
const WORDS_PER_MINUTE = 200;
const readingTime = (s) => {
  const words = s.trim().split(/\s+/).length;
  return (60 * words) / WORDS_PER_MINUTE;
};
console.log("card                      words  reading time (s)");
let longest = 0;
for (const c of CARDS) {
  const t = readingTime(c.title + " " + c.text);
  longest = Math.max(longest, t);
  console.log(`  ${c.title.padEnd(24)} ${String((c.title + " " + c.text).split(/\s+/).length).padStart(4)} ${t.toFixed(1).padStart(14)}`);
}
console.log(`longest card's reading time: ${longest.toFixed(1)} s`);

// 2. Transition-interval decisions and their outcomes.
const INTERVALS = [3, 5, 8, 12];
console.log("\ninterval (s)  is it enough to read the longest card  does WCAG 2.2.2 require it");
for (const a of INTERVALS) {
  console.log(
    `${String(a).padStart(9)} ${(a >= longest ? "yes" : "NO").padStart(37)} ` +
      `${(a > 5 ? "yes (motion over 5 s)" : "yes (auto-starting motion)").padStart(30)}`
  );
}
console.log("rule: whatever the interval, a stop control is mandatory; the interval only");
console.log("      decides whether reading catches up.");

// 3. Tab order: what happens if hidden cards stay in the order?
const totalActions = CARDS.reduce((t, c) => t + c.actions, 0);
const visible = CARDS[0];
console.log("\n--- tab stop count ---");
console.log(`  every card in the order : ${totalActions + 4}  (card actions + prev/next + stop + dot group)`);
console.log(`  only the visible card   : ${visible.actions + 4}`);
console.log(`  difference              : ${totalActions - visible.actions} stops go to content that is not visible`);

// 4. What happens when the transition runs while focus is inside the strip.
const SCENARIOS = [
  { name: "transition keeps running, focus is inside", focusLoss: true, outcome: "focus falls to the document root" },
  { name: "pauses while focus is inside", focusLoss: false, outcome: "user finishes reading" },
  { name: "pauses only on pointer hover", focusLoss: true, outcome: "keyboard user is not protected" },
];
console.log("\n--- auto-advance while focus is inside the strip ---");
console.log("policy                        focus loss  outcome");
for (const s of SCENARIOS) {
  console.log(`  ${s.name.padEnd(30)} ${(s.focusLoss ? "yes" : "no").padEnd(11)} ${s.outcome}`);
}

// 5. Announcement policy: is the automatic transition announced?
function announcementPolicy(automatic) {
  return automatic
    ? { liveRegion: "off", reason: "a change the user did not start is not announced" }
    : { liveRegion: "polite", reason: "content that arrives from the user's own action is announced" };
}
console.log("\n--- announcement policy ---");
for (const automatic of [true, false]) {
  const p = announcementPolicy(automatic);
  console.log(`  transition ${(automatic ? "automatic" : "manual").padEnd(9)} live region: ${p.liveRegion.padEnd(7)} ${p.reason}`);
}

// 6. Position announcement: which card out of how many?
const position = (i, n) => `${i + 1} / ${n}`;
console.log("\nposition announcement examples: " + CARDS.map((_, i) => position(i, CARDS.length)).join("  "));

// 7. Strip versus a grid: what if the same content were shown without scrolling?
console.log("\n--- strip versus grid comparison ---");
console.log(`  cards visible at once in the strip: 1 / ${CARDS.length}`);
console.log(`  cards visible at once in the grid : ${CARDS.length} / ${CARDS.length}`);
console.log(`  content not visible in the strip  : ${(100 * (CARDS.length - 1) / CARDS.length).toFixed(0)}%`);
```

```
card                      words  reading time (s)
  Astronomy Handbook         11            3.3
  Northern Sky Atlas          9            2.7
  Measurement Methods        10            3.0
  Slope Meteorology           9            2.7
  History of the Logbook     10            3.0
longest card's reading time: 3.3 s

interval (s)  is it enough to read the longest card  does WCAG 2.2.2 require it
        3                                    NO     yes (auto-starting motion)
        5                                   yes     yes (auto-starting motion)
        8                                   yes          yes (motion over 5 s)
       12                                   yes          yes (motion over 5 s)
rule: whatever the interval, a stop control is mandatory; the interval only
      decides whether reading catches up.

--- tab stop count ---
  every card in the order : 12  (card actions + prev/next + stop + dot group)
  only the visible card   : 6
  difference              : 6 stops go to content that is not visible

--- auto-advance while focus is inside the strip ---
policy                        focus loss  outcome
  transition keeps running, focus is inside yes         focus falls to the document root
  pauses while focus is inside   no          user finishes reading
  pauses only on pointer hover   yes         keyboard user is not protected

--- announcement policy ---
  transition automatic live region: off     a change the user did not start is not announced
  transition manual    live region: polite  content that arrives from the user's own action is announced

position announcement examples: 1 / 5  2 / 5  3 / 5  4 / 5  5 / 5

--- strip versus grid comparison ---
  cards visible at once in the strip: 1 / 5
  cards visible at once in the grid : 5 / 5
  content not visible in the strip  : 80%
```

The first block of output shows that the longest card reads in a little over three
seconds. This estimate assumes familiar content and fluent reading; for a user listening
through a screen reader, reading with low visual acuity, or unfamiliar with the content,
the time runs many times longer. The estimate is therefore a lower bound, not a target.

The second block evaluates interval options against two measures. A three-second interval
is not enough to read the longest card. Any interval above five seconds puts the motion
into the timed-motion category. What the table actually shows is this: **whatever
interval is chosen, a stop control is mandatory.** The interval only decides whether
reading catches up; the user keeping control of the motion is a separate, unconditional
requirement (WCAG 2.2.2).

## What Hidden Cards Do in the Tab Order

The third block compares tab stop counts. If all five cards are left in the order, twelve
stops are created, and six of them go to content that is **not visible** at that moment.
A keyboard user crossing the strip focuses on links belonging to cards they cannot see;
the focus indicator sits somewhere off screen.

The rule is clear: a card that is not visible is removed from both the tab order and the
accessibility tree. Access to all the cards is provided through the strip's own
navigation controls — previous, next, and the position dots.

## A Transition While Focus Is Inside

The fourth block compares three policies. If the transition keeps running, the focused
element disappears; focus falls to the document root and the user loses their place.
This is a violation of the focus-return rule from the Modal Dialogs lesson: if the
element carrying focus is being removed, where focus goes next has to be decided in
advance.

Pausing only on pointer hover does not protect a keyboard user. The correct policy is
that **the transition stops while focus is inside the strip**; once the user finishes
reading and moves focus out, the cycle resumes where it left off.

## Is Automatically Changing Content Announced

The fifth block separates announcement policy. A change the user did not start is not
announced: announcing the new card on every transition would keep interrupting whatever a
screen-reader user is reading at that moment. The strip stays silent during an automatic
transition.

The situation reverses when the user presses the previous or next button: the source of
the change is the user's own action, and its result has to be reported. The position
announcement in the sixth block is the content of that report — which card, out of how
many, is being shown. The position dots carry the same information and are therefore not
purely decorative.

## Keyboard Contract

| Key | Behavior |
|---|---|
| `Tab` | Reaches the strip's controls and the actions of **only the visible** card |
| `Enter` / `Space` | Runs the control under focus: previous, next, stop, position dot |
| Arrow keys | Moves between cards within the position-dot group (roving tabindex) |

When the strip itself is built like a set of tabs, the arrow-key rule is the same
contract as the Tabs lesson; if the card content is a link list, there is no arrow-key
rule.

## Measurable Constraints

- Auto-starting motion that runs longer than five seconds requires a pause, stop, or
  hide control (WCAG 2.2.2).
- When reduced motion is signaled, the automatic transition **does not start**; the rule
  from the User Preferences lesson applies here too.
- Cards that are not visible are absent from both the tab order and the accessibility
  tree.
- Position dots' target size cannot be smaller than 24×24 pixels (WCAG 2.5.8); if a dot
  is visually small, its hit area is enlarged.
- The live region is off during an automatic transition; a transition from a user action
  is announced from a polite region.

## Common Mistake

The most common mistake is not adding a stop control at all, or tying it only to pointer
hover. The second is treating the position dots as decoration and leaving them unnamed:
if the "1 / 5" information exists on no channel, the user cannot tell how many cards
there are. The third is using the strip for content that must not be missed — a campaign
announcement, a warning, or one-time information placed in a strip drops to a one-in-five
chance of being seen.

## Summary

- Cyclical navigation is used only for content that is an alternative to itself, whose
  omission causes no loss of information; at any moment, eighty percent of the strip's
  content is not visible.
- The transition interval is chosen against reading time (the longest card in the model
  reads in 3.3 seconds), but whatever the interval, a stop control is unconditionally
  mandatory.
- Cards that are not visible are removed from the tab order: in a five-card strip, this
  decision takes six stops away from content the user cannot see.
- The automatic transition stops while focus is inside the strip; a pause tied only to
  the pointer does not protect a keyboard user.
- The automatic transition is not announced; a transition from a user action is announced
  with a "which one out of how many" position.

## Next Step

Every component so far stayed inside the page's flow: the card, the tabs, the table, and
the strip each held their own place, and the user went to them. The components ahead rise
**above** that flow and often take over focus: a borrowing confirmation is asked inside a
modal dialog, a result is delivered through a notification banner, a wait is communicated
with an indicator. The next topic takes on this layer and opens with modal dialogs — how
focus is contained, how the window is closed, and where focus returns to when it closes.
