Skip to content

Comments

[CLX-3694][Horizon] Accessibility fixes#3454

Merged
andrasmaczak merged 2 commits intomasterfrom
CLX-3694-accessibility-issues
Dec 18, 2025
Merged

[CLX-3694][Horizon] Accessibility fixes#3454
andrasmaczak merged 2 commits intomasterfrom
CLX-3694-accessibility-issues

Conversation

@andrasmaczak
Copy link
Contributor

refs: CLX-3694
affects: Student
release note: none

@github-actions
Copy link

github-actions bot commented Dec 17, 2025

🧪 Unit Test Results

✅ 📱 Student App

  • Tests: 1241 total, 0 failed, 0 skipped
  • Duration: 0.000s
  • Success Rate: 100%

✅ 🌅 Horizon

  • Tests: 449 total, 0 failed, 0 skipped
  • Duration: 34.682s
  • Success Rate: 100%

✅ 📦 Submodules

  • Tests: 2472 total, 0 failed, 0 skipped
  • Duration: 43.177s
  • Success Rate: 100%

📊 Summary

  • Total Tests: 4162
  • Failed: 0
  • Skipped: 0
  • Status: ✅ All tests passed!

Last updated: Thu, 18 Dec 2025 19:51:52 GMT

Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessibility Improvements Review

This PR makes significant accessibility improvements to the Horizon UI components. The changes primarily focus on adding proper semantic properties, content descriptions, and improved screen reader support. Overall, this is excellent work that will greatly improve the app's accessibility.

Issues Found

  • Extensions.kt:57 - toggleable function's onClick action always returns false and doesn't perform actual toggle operation
  • Extensions.kt:44 - Redundant liveRegion assignment in selectable function (set both conditionally and unconditionally)
  • ProgressScreen.kt:142 - Incorrect use of remember with var hasPageChanged - should use mutableStateOf for proper state tracking
  • DashboardWidgetCard.kt:87 & DashboardCourseSection.kt:161 - Empty contentDescription = "" usage should be verified as intentional

Positive Highlights

  • Comprehensive coverage: Accessibility improvements span across dashboard, module sequences, and various UI components
  • Proper semantic properties: Good use of selectable, toggleable, hideFromAccessibility, and other semantic modifiers
  • Content descriptions: IconButtons and interactive elements now have appropriate content descriptions
  • Consistent patterns: The new selectable and toggleable extension functions promote code reusability
  • Live regions: Proper use of LiveRegionMode.Assertive for announcing state changes
  • Focus management: Smart focus handling in ProgressScreen for improved keyboard/screen reader navigation
  • String resources: All new accessibility strings are properly externalized

Additional Observations

  1. Modifier threading: Great job threading the modifier parameter through component hierarchies (DashboardAnnouncementBannerWidget, DashboardCourseSection, etc.)
  2. Code cleanup: Removed unused Spacer import and cleaned up formatting
  3. Separator accessibility: Nice attention to detail hiding the "|" separators from screen readers in ModuleItemSequenceScreen

Recommendations

  1. Test with TalkBack extensively to ensure all changes work as expected
  2. Verify the empty content description patterns don't inadvertently hide important information
  3. Consider adding accessibility testing to prevent regressions

Great work on improving the accessibility of the Canvas Android app!

stateDescription = if (toggledOn) onStateDesc else offStateDesc
liveRegion = LiveRegionMode.Assertive
onClick(toggleActionLabel) { false }
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The onClick action in the toggleable function always returns false, which means it won't actually handle the click event. This appears to be a placeholder implementation that should either:

  1. Return true to indicate the action was handled
  2. Be removed if the actual click handling is done elsewhere

Additionally, the toggleActionLabel is defined but the action doesn't actually perform any toggle operation. Consider if this semantic action is needed, or if the toggle handling is done at a higher level in the composable hierarchy.


@Composable
private fun BoxScope.ProgressScreenContent(uiState: ProgressScreenUiState) {
val currentPage = uiState.pages[uiState.currentPosition]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a logic issue with hasPageChanged. It's declared as a var but remember expects an immutable initial value. This should use remember { mutableStateOf(false) } instead:

var hasPageChanged by remember { mutableStateOf(false) }

Or if you want to use a simple boolean:

val hasPageChanged = remember { mutableStateOf(false) }

The current implementation may not properly track state changes across recompositions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}.conditional(!isLoading) {
semantics {
contentDescription = ""
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting contentDescription = "" when not loading may interfere with accessibility. An empty content description can sometimes be problematic for screen readers. Consider using clearAndSetSemantics { } instead if you want to clear all semantics, or verify that an empty string is the intended behavior for this use case.

If the goal is to ensure child elements provide their own content descriptions, this approach is correct. Just wanted to flag for verification.

if (cardHeight > maxCardHeight) { maxCardHeight = cardHeight }
}
.semantics {
contentDescription = ""
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same concern as with DashboardWidgetCard - setting contentDescription = "" may not be the best approach. Verify this is intentional and doesn't hide important information from screen readers.

Additionally, this semantic block is only applied in the pager context when page < pagerState.pageCount. Consider whether this semantic clearing should also apply to other cases or if there's a reason for this conditional application.

val unselectedStateDesc = context.getString(R.string.a11y_unselected)

stateDescription = if (selected) selectedStateDesc else unselectedStateDesc
if (selected) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The liveRegion = LiveRegionMode.Assertive appears twice in this function - once on line 44 (outside the conditional) and once on line 46 (inside the if (selected) block). This is redundant.

If the intention is to always set it to Assertive for selectable items, keep only the one on line 44. If it should only be Assertive when selected, remove line 44 and keep only line 46.

@github-actions
Copy link

github-actions bot commented Dec 17, 2025

Student Install Page

@github-actions
Copy link

📊 Code Coverage Report

ℹ️ Student

  • PR Coverage: 42.91%
  • Master Coverage: N/A

ℹ️ Teacher

  • PR Coverage: 25.48%
  • Master Coverage: N/A

ℹ️ Pandautils

  • PR Coverage: 22.49%
  • Master Coverage: N/A

@andrasmaczak andrasmaczak merged commit 25d3cbc into master Dec 18, 2025
47 of 48 checks passed
@andrasmaczak andrasmaczak deleted the CLX-3694-accessibility-issues branch December 18, 2025 23:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants