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
commit b92b78ec84
7 changed files with 55 additions and 25 deletions

View file

@ -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,

View file

@ -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,

View file

@ -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 },

View file

@ -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,

View file

@ -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 },

View file

@ -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,

View file

@ -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>