Common Focus Order Issues in Pregnancy Apps: Causes and Fixes
* User complaints – On the Google Play Store, several pregnancy trackers have a 3‑star average with recurring reviews: “I can’t get to the ‘Add appointment’ button without tapping the whole screen thr
1. What causes focus‑order issues in pregnancy apps
| Root cause | Why it shows up in pregnancy apps |
|---|---|
| Hard‑coded tab indices | Many UI designers copy‑paste XML/HTML snippets for weekly trackers, symptom logs, or hormone charts. If each copy retains a tabindex value, the logical order is broken when the screen grows. |
| Dynamic list inflation | Weekly “baby‑development” lists, medication tables, and push‑notification cards are built at runtime. When items are inserted with addView() (Android) or appendChild() (Web) without updating the focus chain, newly added rows inherit the default order, which is often after the last focusable element on the page, not after the previous row. |
| Custom view components | Pregnancy apps frequently ship custom pickers for due‑date, contraction timers, or weight‑gain sliders. If the component does not expose proper accessibility focus events (requestFocus(), focusableInTouchMode), screen readers will skip it or place it at the end of the order. |
| Conditional UI branches | “If you’re in the first trimester, show diet tips; otherwise hide them.” Hiding/showing sections with visibility=gone (Android) or display:none (Web) does not automatically remove them from the focus order on all platforms, leading to “phantom” focus stops. |
| Third‑party SDK overlays | Advertising or analytics SDKs often inject floating buttons (e.g., “Rate us”, “Share”). Because they are added after the main layout, they appear at the end of the tab order, stealing focus from critical form fields like “Enter due date”. |
| Inconsistent platform conventions | Android expects a left‑to‑right, top‑to‑bottom order; iOS VoiceOver follows a similar but not identical rule set. When a cross‑platform framework (React Native, Flutter) is used without explicit focus‑order mapping, the generated order can differ per OS, confusing users who switch devices. |
| Lack of semantic grouping | Pregnancy apps contain many related controls (e.g., “Morning sickness severity” radio group). If they are not wrapped in a proper group (android:accessibilityTraversalAfter, role="radiogroup"), the focus jumps between unrelated elements, breaking mental models. |
---
2. Real‑world impact
- User complaints – On the Google Play Store, several pregnancy trackers have a 3‑star average with recurring reviews: “I can’t get to the ‘Add appointment’ button without tapping the whole screen three times” or “VoiceOver jumps from the week‑7 picture to the settings menu, I miss the nutrition tip.”
- Store ratings – Apps that score below 4.0 often have ≥15 % of their negative reviews mentioning “focus”, “keyboard”, or “screen reader”. This directly influences discoverability because Play Store and App Store ranking algorithms weigh recent review sentiment.
- Revenue loss – A typical freemium pregnancy app converts 6 % of users to a paid premium plan. Studies show that each 0.5‑star drop in rating can shave ≈8 % off monthly recurring revenue (MRR). For an app with $30 k / month MRR, that’s a $2.4 k loss per month attributable to accessibility friction, most of which stems from focus‑order problems.
---
3. Five concrete examples of focus‑order issues in pregnancy apps
- Weekly development carousel skips the “Save” button
- The carousel is built with a
ViewPager2that lazily inflates each week’s view. The “Save” floating action button (FAB) is declared after theViewPager2in the XML, so when a user tabs through the week cards, focus lands on the next screen’s header instead of the FAB.
- Symptom‑tracker checklist leaves a dead focus gap
- Each symptom row (
) is generated from a JSON array. When a user adds a new custom symptom, the app inserts the view at the end of the container but does not update theandroid:accessibilityTraversalAfterchain, causing a “hole” where focus lands on the background view for a split second before moving on.
- Due‑date picker modal steals focus from the “Next” button
- The date picker is displayed via a third‑party library that adds a full‑screen overlay. The overlay’s close button receives the first focus, but the underlying “Next” button (which submits the form) is never reachable via Tab, forcing users to dismiss the overlay manually.
- Nutrition‑tips side panel appears after the main form
- When the user scrolls to the bottom of the “Daily Log” screen, a side panel slides in with suggested foods. Because the panel is added to the DOM after the main form, its interactive elements (e.g., “Add to grocery list”) appear after the form’s submit button in the tab order, confusing users who expect them before submission.
- Cross‑session “Share progress” button hidden on iOS
- The share button is rendered conditionally (
if (platform === 'android')). On iOS the button is hidden withdisplay:none, but the element remains in the accessibility tree, creating a phantom focus stop that forces VoiceOver users to swipe twice for no result.
---
4. How to detect focus‑order issues
| Detection method | What to look for | How SUSA helps |
|---|---|---|
| Manual exploratory testing | Use a hardware keyboard (Android) or Tab key (Web) to move focus. Verify that focus moves logically from top‑left to bottom‑right, and that every interactive element receives focus exactly once. | Upload the APK or URL to SUSA. The platform runs a persona called *curious* that simulates Tab navigation across every screen, logging any element that is skipped or receives focus out of order. |
| Screen‑reader walkthrough | Enable TalkBack (Android) or VoiceOver (iOS) and listen to the announced order. Look for “focus moved to background” or “skipped element”. | SUSA’s *accessibility* persona performs WCAG 2.1 AA testing, automatically flagging “focus order” violations and providing a per‑screen “untapped element” list. |
| Automated accessibility audit tools | Axe, Lighthouse, or Android Lint can surface “focusable elements not reachable via sequential navigation”. | SUSA generates Appium regression scripts that include driver.getPageSource() after each navigation step, then asserts that the focus traversal sequence matches the visual order. |
| Unit‑level focus‑order assertions | In UI tests, call assertFocusOrder(expectedIds) after each screen is rendered. | The CLI (pip install susatest-agent) can inject a “focus‑order validator” hook into your CI pipeline, emitting JUnit XML failures whenever the runtime order diverges from the declared order. |
| Cross‑session learning | Run the app multiple times with different data sets (e.g., different trimesters). Observe if new dynamic rows break the order. | SUSA’s cross‑session learning remembers previously discovered focus gaps and re‑checks them on each new run, surfacing regressions instantly. |
---
5. Fixes – code‑level guidance for each example
1️⃣ Weekly development carousel
*Android (Kotlin)*
// After inflating the ViewPager2, set the FAB's traversal order
fab.isFocusable = true
ViewCompat.setAccessibilityTraversalAfter(fab, viewPager.id)
*Web (React)*
// Ensure the FAB receives a lower tabindex than the carousel
<button id="saveBtn" tabIndex={0}>Save</button>
<div id="carousel" role="region" tabIndex={-1}>…</div>
2️⃣ Symptom‑tracker checklist
// When adding a new symptom row
val newRow = LayoutInflater.from(context).inflate(R.layout.symptom_row, container, false)
container.addView(newRow)
// Update traversal chain
if (container.childCount > 1) {
val prev = container.getChildAt(container.childCount - 2)
ViewCompat.setAccessibilityTraversalAfter(newRow, prev.id)
}
// React – keep a refs array and set `tabIndex` sequentially
symptoms.map((s,i) => (
<input
key={s.id}
ref={el => refs.current[i] = el}
tabIndex={i}
type="checkbox"
aria-label={s.label}
/>
));
3️⃣ Due‑date picker modal
*Android (XML)*
<!-- Ensure the modal’s close button is first, then the underlying Next button -->
<Button
android:id="@+id/closeModal"
android:accessibilityTraversalAfter="@id/datePicker"
/>
<Button
android:id="@+id/nextBtn"
android:accessibilityTraversalAfter="@id/closeModal"
/>
*Web (HTML)*
<div role="dialog" aria-modal="true" id="datePicker">
<button id="closeDialog">Close</button>
<!-- hidden but focusable Next button inside the dialog -->
<button id="nextBtn" tabindex="0">Next</button>
</div>
4️⃣ Nutrition‑tips side panel
// When sliding the panel in, move it before the submit button in the view hierarchy
val parent = submitButton.parent as ViewGroup
parent.removeView(sidePanel)
parent.addView(sidePanel, parent.indexOfChild(submitButton))
// Web – insert the panel before the submit button in the DOM
const form = document.getElementById('dailyLog')
form.insertBefore(sidePanel, form.querySelector('#submitBtn'))
5️⃣ Cross‑session “Share progress” button
// iOS – completely remove from accessibility tree when hidden
shareButton.isHidden = true
shareButton.isAccessibilityElement = false
// Android – use `visibility = View.GONE` *and* `importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO`
shareButton.visibility = View.GONE
shareButton.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
---
6. Prevention – catching focus‑order problems before release
- Define a focus‑order map early
- Create a CSV or JSON file that lists every screen and the expected focus sequence (
screenId → [elementId1, elementId2, …]). Treat it as a source of truth and version‑control it alongside UI code.
- Integrate SUSA into CI/CD
- Add the
susatest-agentCLI to your GitHub Actions workflow.
- name: Run SUSA accessibility suite
run: |
susatest-agent run --apk app/build/outputs/apk/release/app-release.apk \
--persona accessibility \
--output junit.xml
- uses: actions/upload-artifact@v3
with:
name: susa-results
path: junit.xml
- Automated regression script generation
- After each successful build, let SUSA generate Appium (Android) and Playwright (Web) scripts that include
driver.switchTo().activeElement()checks. Store these scripts in yourtests/folder and run them on every pull request.
- Component‑level unit tests
- For every custom view (date picker, symptom row, carousel), write a small Espresso or XCTest that asserts the view is focusable and that
nextFocusDownIdpoints to the correct neighbour.
- Persona‑driven exploratory sprints
- Schedule a weekly “accessibility sprint” where a QA engineer runs the *elderly* and *novice* personas in SUSA. These personas use slower navigation speeds and larger touch targets, exposing hidden traversal bugs before users do.
- Documentation & lint rules
- Enforce a linter rule (e.g.,
androidx.lint:accessibility) that warns when a view hasandroid:focusable="true"without a correspondingandroid:accessibilityTraversalAfterorandroid:accessibilityTraversalBefore.
- Cross‑platform consistency checks
- When using React Native or Flutter, enable the framework’s accessibility inspector (e.g.,
react-native-accessibility-info) and compare the generated focus order to the platform‑specific map.
By making focus order a first‑class requirement—backed by automated detection (SUSA), CI enforcement, and component‑level tests—pregnancy apps can eliminate the navigation friction that currently drives down ratings, increase conversion to premium plans, and, most importantly, provide a safe, inclusive experience for expecting parents of all abilities.
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free