Lesson 13 / 25
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.
Contents
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.
// 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.
To keep your progress and take notes, Log in
My notes
Log in to take notes.