Merge pull request #6827 from element-hq/feature/bma/a11y/roomNameHeading

[a11y] Improve accessibility of screen headers.
This commit is contained in:
Benoit Marty
2026-05-21 09:00:33 +02:00
committed by GitHub
7 changed files with 55 additions and 25 deletions
@@ -609,6 +609,7 @@ private fun JoinRoomTopBar(
val roundedCornerShape = RoundedCornerShape(8.dp) val roundedCornerShape = RoundedCornerShape(8.dp)
val titleModifier = Modifier val titleModifier = Modifier
.clip(roundedCornerShape) .clip(roundedCornerShape)
.semantics { heading() }
if (contentState.name != null) { if (contentState.name != null) {
Row( Row(
modifier = titleModifier, modifier = titleModifier,
@@ -621,10 +622,7 @@ private fun JoinRoomTopBar(
) )
Text( Text(
modifier = Modifier modifier = Modifier
.padding(horizontal = 8.dp) .padding(horizontal = 8.dp),
.semantics {
heading()
},
text = contentState.name, text = contentState.name,
style = ElementTheme.typography.fontBodyLgMedium, style = ElementTheme.typography.fontBodyLgMedium,
maxLines = 1, maxLines = 1,
@@ -31,6 +31,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
@@ -79,7 +82,18 @@ fun ThreadsListView(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { title = {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { val description = stringResource(
CommonStrings.a11y_threads_in_room,
state.roomName,
)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clearAndSetSemantics {
heading()
contentDescription = description
},
) {
Avatar( Avatar(
avatarData = AvatarData( avatarData = AvatarData(
id = state.roomId.value, id = state.roomId.value,
@@ -80,7 +80,8 @@ internal fun MessagesViewTopBar(
Row( Row(
modifier = Modifier modifier = Modifier
.clip(roundedCornerShape) .clip(roundedCornerShape)
.clickable { onRoomDetailsClick() }, .clickable { onRoomDetailsClick() }
.semantics { heading() },
horizontalArrangement = Arrangement.spacedBy(4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
@@ -158,10 +159,7 @@ private fun RoomAvatarAndNameRow(
) )
Text( Text(
modifier = Modifier modifier = Modifier
.padding(start = 8.dp) .padding(start = 8.dp),
.semantics {
heading()
},
text = roomName ?: stringResource(CommonStrings.common_no_room_name), text = roomName ?: stringResource(CommonStrings.common_no_room_name),
style = ElementTheme.typography.fontBodyLgMedium, style = ElementTheme.typography.fontBodyLgMedium,
fontStyle = FontStyle.Italic.takeIf { roomName == null }, fontStyle = FontStyle.Italic.takeIf { roomName == null },
@@ -17,8 +17,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -58,7 +59,18 @@ internal fun ThreadTopBar(
BackButton(onClick = onBackClick) BackButton(onClick = onBackClick)
}, },
title = { title = {
Row(verticalAlignment = Alignment.CenterVertically) { val name = roomName ?: stringResource(CommonStrings.common_no_room_name)
val description = stringResource(
CommonStrings.a11y_thread_in_room,
name,
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clearAndSetSemantics {
heading()
contentDescription = description
},
) {
Avatar( Avatar(
avatarData = roomAvatarData, avatarData = roomAvatarData,
avatarType = AvatarType.Room( avatarType = AvatarType.Room(
@@ -69,17 +81,14 @@ internal fun ThreadTopBar(
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 8.dp) .padding(horizontal = 8.dp),
.semantics {
heading()
},
) { ) {
Text( Text(
text = stringResource(CommonStrings.common_thread), text = stringResource(CommonStrings.common_thread),
style = ElementTheme.typography.fontBodyLgMedium, style = ElementTheme.typography.fontBodyLgMedium,
) )
Text( Text(
text = roomName ?: stringResource(CommonStrings.common_no_room_name), text = name,
style = ElementTheme.typography.fontBodySmRegular, style = ElementTheme.typography.fontBodySmRegular,
fontStyle = FontStyle.Italic.takeIf { roomName == null }, fontStyle = FontStyle.Italic.takeIf { roomName == null },
color = ElementTheme.colors.textSecondary, color = ElementTheme.colors.textSecondary,
@@ -354,7 +354,8 @@ private fun EmptySpaceView(
title = stringResource(R.string.screen_space_empty_state_title), title = stringResource(R.string.screen_space_empty_state_title),
subTitle = null, subTitle = null,
iconStyle = BigIcon.Style.Default(vectorIcon = CompoundIcons.Room(), usePrimaryTint = true), iconStyle = BigIcon.Style.Default(vectorIcon = CompoundIcons.Room(), usePrimaryTint = true),
modifier = Modifier.fillMaxWidth() modifier = Modifier
.fillMaxWidth()
.padding(top = 40.dp, start = 24.dp, end = 24.dp, bottom = 24.dp), .padding(top = 40.dp, start = 24.dp, end = 24.dp, bottom = 24.dp),
) )
ButtonColumnMolecule( ButtonColumnMolecule(
@@ -425,6 +426,7 @@ private fun SpaceViewTopBar(
modifier = Modifier modifier = Modifier
.clip(roundedCornerShape) .clip(roundedCornerShape)
.clickable(enabled = canAccessSpaceSettings, onClick = onSettingsClick) .clickable(enabled = canAccessSpaceSettings, onClick = onSettingsClick)
.semantics { heading() }
) )
}, },
actions = { actions = {
@@ -532,6 +534,7 @@ private fun ManageModeTopBar(
Text( Text(
text = pluralStringResource(CommonPlurals.common_selected_count, selectedCount, selectedCount), text = pluralStringResource(CommonPlurals.common_selected_count, selectedCount, selectedCount),
style = ElementTheme.typography.fontBodyLgMedium, style = ElementTheme.typography.fontBodyLgMedium,
modifier = Modifier.semantics { heading() },
) )
}, },
actions = { actions = {
@@ -585,10 +588,7 @@ private fun SpaceAvatarAndNameRow(
) )
Text( Text(
modifier = Modifier modifier = Modifier
.padding(horizontal = 8.dp) .padding(horizontal = 8.dp),
.semantics {
heading()
},
text = name ?: stringResource(CommonStrings.common_no_space_name), text = name ?: stringResource(CommonStrings.common_no_space_name),
style = ElementTheme.typography.fontBodyLgMedium, style = ElementTheme.typography.fontBodyLgMedium,
fontStyle = FontStyle.Italic.takeIf { name == null }, fontStyle = FontStyle.Italic.takeIf { name == null },
@@ -55,6 +55,8 @@ import androidx.compose.ui.layout.onVisibilityChanged
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -495,14 +497,20 @@ private fun MediaViewerTopBar(
TopAppBar( TopAppBar(
title = { title = {
if (senderName != null && dateSent != null) { if (senderName != null && dateSent != null) {
val description = stringResource(
CommonStrings.a11y_sent_by_sender_at_date,
senderName,
dateSent,
)
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clearAndSetSemantics {
heading()
contentDescription = description
},
) { ) {
Text( Text(
modifier = Modifier.semantics {
heading()
},
text = senderName, text = senderName,
style = ElementTheme.typography.fontBodyMdMedium, style = ElementTheme.typography.fontBodyMdMedium,
color = ElementTheme.colors.textPrimary, color = ElementTheme.colors.textPrimary,
@@ -50,12 +50,15 @@
<string name="a11y_room_avatar">"Room avatar"</string> <string name="a11y_room_avatar">"Room avatar"</string>
<string name="a11y_send_files">"Send files"</string> <string name="a11y_send_files">"Send files"</string>
<string name="a11y_sender_location">"Sender location"</string> <string name="a11y_sender_location">"Sender location"</string>
<string name="a11y_sent_by_sender_at_date">"Sent by %1$s at %2$s"</string>
<string name="a11y_session_verification_time_limited_action_required">"Time limited action required, you have one minute to verify"</string> <string name="a11y_session_verification_time_limited_action_required">"Time limited action required, you have one minute to verify"</string>
<string name="a11y_settings_with_required_action">"Settings, action required"</string> <string name="a11y_settings_with_required_action">"Settings, action required"</string>
<string name="a11y_show_password">"Show password"</string> <string name="a11y_show_password">"Show password"</string>
<string name="a11y_start_call">"Start a call"</string> <string name="a11y_start_call">"Start a call"</string>
<string name="a11y_start_video_call">"Start a video call"</string> <string name="a11y_start_video_call">"Start a video call"</string>
<string name="a11y_start_voice_call">"Start a voice call"</string> <string name="a11y_start_voice_call">"Start a voice call"</string>
<string name="a11y_thread_in_room">"Thread in %1$s"</string>
<string name="a11y_threads_in_room">"Threads in %1$s"</string>
<string name="a11y_tombstoned_room">"Tombstoned room"</string> <string name="a11y_tombstoned_room">"Tombstoned room"</string>
<string name="a11y_user_avatar">"User avatar"</string> <string name="a11y_user_avatar">"User avatar"</string>
<string name="a11y_user_menu">"User menu"</string> <string name="a11y_user_menu">"User menu"</string>