From 2e8efaec2bc45e0aa0b0e5524ef4f8e54f9498e3 Mon Sep 17 00:00:00 2001 From: LeanBitLab <245915690+LeanBitLab@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:17:53 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Improve=20screen=20re?= =?UTF-8?q?ader=20a11y=20for=20custom=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `role = Role.Button` to `Modifier.clickable` across custom UI buttons in `VideoPage.kt` to ensure screen readers identify them as interactive elements. - Set `contentDescription = null` for the ExpandLess/ExpandMore icon where it sits next to descriptive text, avoiding duplicate screen reader announcements. --- .jules/palette.md | 9 +++------ .../com/example/reddittube/ui/main/VideoPage.kt | 16 +++++++++------- .../ui/main/components/MinimalButton.kt | 1 - 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.jules/palette.md b/.jules/palette.md index a021571..ab3f352 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -1,6 +1,3 @@ -## 2024-05-18 - MinimalButton Accessibility -**Learning:** Custom clickable components without a specific Semantic Role assigned will not be announced correctly by screen readers. Applying `role = Role.Button` to the `clickable` modifier ensures it is recognized correctly as a button. -**Action:** Add `role = Role.Button` to the `clickable` modifier for custom interactive button components. -## 2024-05-18 - Redundant Icon Content Descriptions -**Learning:** Adding `contentDescription` to icons that are directly adjacent to `Text` components containing the same wording is an anti-pattern. Screen readers will announce the information twice (e.g., "Liked, Liked"), creating a noisy experience. Icons in this context should remain decorative (`contentDescription = null`). -**Action:** Verify if text alternatives exist near visual elements before assigning a `contentDescription` to avoid duplicate screen reader announcements. +## 2024-05-24 - Double announcements in Jetpack Compose custom buttons +**Learning:** When creating custom buttons using `clickable` in Jetpack Compose that contain both text and an icon, providing `contentDescription` on the icon can result in double announcements for screen readers if the text already conveys the meaning. Additionally, `clickable` elements acting as buttons must explicitly have `role = Role.Button` to be properly identified by screen readers. +**Action:** When implementing custom buttons with `clickable`, always add `role = Role.Button`. For decorative icons adjacent to text with the same meaning, use `contentDescription = null` to avoid redundant screen reader announcements. diff --git a/app/src/main/java/com/example/reddittube/ui/main/VideoPage.kt b/app/src/main/java/com/example/reddittube/ui/main/VideoPage.kt index ef24e33..23f3ceb 100644 --- a/app/src/main/java/com/example/reddittube/ui/main/VideoPage.kt +++ b/app/src/main/java/com/example/reddittube/ui/main/VideoPage.kt @@ -66,6 +66,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.CustomAccessibilityAction @@ -729,7 +730,7 @@ Box( .clip(CircleShape) .background(Color.Black.copy(alpha = 0.65f)) .border(1.dp, GlassBorder, CircleShape) - .clickable { + .clickable(role = Role.Button) { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) if (exoPlayer.playbackState == Player.STATE_ENDED) { exoPlayer.seekTo(0) @@ -786,7 +787,7 @@ Box( .size(40.dp) .clip(CircleShape) .background(Color.Black.copy(alpha = 0.55f)) - .clickable { onBack?.invoke() }, + .clickable(role = Role.Button) { onBack?.invoke() }, contentAlignment = Alignment.Center ) { Icon(Icons.Default.ArrowBack, contentDescription = "Back", tint = Color.White, modifier = Modifier.size(22.dp)) @@ -799,7 +800,7 @@ Box( modifier = Modifier .fillMaxSize() .background(Color.Black.copy(alpha = 0.85f)) - .clickable { + .clickable(role = Role.Button) { showOnboarding = false sharedPreferences.edit().putBoolean("has_seen_onboarding", true).apply() }, @@ -868,7 +869,7 @@ Box( color = BrandRed, fontSize = 16.sp, fontWeight = FontWeight.Bold, - modifier = Modifier.clickable { onSubredditClick(post.subreddit) } + modifier = Modifier.clickable(role = Role.Button) { onSubredditClick(post.subreddit) } ) Spacer(modifier = Modifier.width(6.dp)) val isSubbed = subscribedSet.contains(post.subreddit.lowercase()) @@ -877,7 +878,7 @@ Box( .size(32.dp) .clip(CircleShape) .background(BrandRed.copy(alpha = if (isSubbed) 0.2f else 0.15f)) - .clickable { + .clickable(role = Role.Button) { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) onSubscribeToggle(post.subreddit.lowercase()) }, @@ -912,6 +913,7 @@ Box( .clickable( interactionSource = remember { androidx.compose.foundation.interaction.MutableInteractionSource() }, indication = null, + role = Role.Button, onClick = { if (isLongTitle) isTitleExpanded = !isTitleExpanded } ) ) { @@ -928,7 +930,7 @@ Box( Spacer(modifier = Modifier.height(3.dp)) Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.clickable { isTitleExpanded = !isTitleExpanded } + modifier = Modifier.clickable(role = Role.Button) { isTitleExpanded = !isTitleExpanded } ) { Text( text = if (isTitleExpanded) "Show less" else "Show more", @@ -938,7 +940,7 @@ Box( ) Icon( imageVector = if (isTitleExpanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore, - contentDescription = if (isTitleExpanded) "Show less" else "Show more", + contentDescription = null, tint = TextSecondary, modifier = Modifier.size(15.dp) ) diff --git a/app/src/main/java/com/example/reddittube/ui/main/components/MinimalButton.kt b/app/src/main/java/com/example/reddittube/ui/main/components/MinimalButton.kt index 056265a..8879150 100644 --- a/app/src/main/java/com/example/reddittube/ui/main/components/MinimalButton.kt +++ b/app/src/main/java/com/example/reddittube/ui/main/components/MinimalButton.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp