{"id":25821,"date":"2023-12-08T05:00:00","date_gmt":"2023-12-08T04:00:00","guid":{"rendered":"https:\/\/sii.pl\/blog\/?p=25821"},"modified":"2024-07-22T14:22:53","modified_gmt":"2024-07-22T12:22:53","slug":"accessibility-in-compose","status":"publish","type":"post","link":"https:\/\/sii.pl\/blog\/en\/accessibility-in-compose\/","title":{"rendered":"Accessibility in Compose"},"content":{"rendered":"\n<p>Accessibility tools, such as TalkBack, use a semantics tree to learn the hierarchy and parameters of elements. Therefore, developers have no direct control over accessibility interfaces (what Talkback reads). It is the developer&#8217;s job to provide the appropriate context and user interface information for accessibility services.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Semantics tree<\/strong><\/h2>\n\n\n\n<p>Since the plan composing functions displayed as UIs are not exactly hierarchical, accessibility tools have no way to identify a specific component after the UI is emitted. The solution to this problem is a semantic tree constructed with a composition tree. The difference is that the semantic tree is accessible via accessibility services such as TalkBack.<br>A developer can define specific semantic properties, this is done by<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nModifier.semantics()\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\"><strong>How does semantics help with accessibility?<\/strong><\/h2>\n\n\n\n<p>As described above, semantics provides TalkBack and other accessibility tools with the context of elements on the screen and their properties.<\/p>\n\n\n\n<p>Semantics are covered by most of the compose elements. There are also ways to override them or extend them. You can clear semantics with:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nModifier.clearAndSetSematics()\n<\/pre><\/div>\n\n\n<p>or merge parent with child elements with:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nModifier.semantics( mergeDescendants = true ) {}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\"><strong>Most commonly used sematic properties and patterns<\/strong><\/h2>\n\n\n\n<p>The following describes accessibility solutions and approaches I came across while working on UI in a large banking application in Compose.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Logically single element<\/strong><\/h3>\n\n\n\n<p>Whenever an element consists of multiple UI elements, but presents itself as logically one, it should be displayed to accessibility tools ale single. Example below:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\n@Composable\nfun ListItemWithAvatar(painter: Painter, text: String) {\n    Row(\n        modifier = Modifier.semantics(mergeDescendants = true) {\n        }\n    ) {\n        Image(painter = painter, contentDescription = null)\n        Text(text = text)\n    }\n}\n\n<\/pre><\/div>\n\n\n<p>On image below shown version with mergeDescendants = true and mergeDescendants = false<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/1-3.jpg\"><img decoding=\"async\" width=\"329\" height=\"113\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/1-3.jpg\" alt=\"\" class=\"wp-image-25822\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/1-3.jpg 329w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/1-3-300x103.jpg 300w\" sizes=\"(max-width: 329px) 100vw, 329px\" \/><\/a><\/figure>\n\n\n\n<div style=\"height:20px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Clickable and selectable elements<\/strong><\/h3>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img decoding=\"async\" width=\"320\" height=\"720\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/1.gif\" alt=\"\" class=\"wp-image-25854\"\/><\/figure>\n\n\n\n<p>Compose provides specific modifiers that support all required semantics for clickable and selectable elements.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nfun Modifier.clickable(\n    interactionSource: MutableInteractionSource,\n    indication: Indication?,\n    enabled: Boolean = true,\n    onClickLabel: String? = null,\n    role: Role? = null,\n    onClick: () -&gt; Unit\n)\nfun Modifier.selectable(\n    selected: Boolean,\n    interactionSource: MutableInteractionSource,\n    indication: Indication?,\n    enabled: Boolean = true,\n    role: Role? = null,\n    onClick: () -&gt; Unit\n)\n\n<\/pre><\/div>\n\n\n<p>Note that their use depends on the element type. For example, if you have set SemanticsProperties.Text or SemanticsProperties.ContentDescription, you probably do not want to add onClickLabel. You can see this in the compose button implementation, where onClickLabel is predefined as null.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Expandable content description<\/strong><\/h3>\n\n\n\n<p>Those options can be used with accordions, expandable texts and other expandable elements. When an element expands visually and reveals other elements on the screen, it should be marked in semantics in an appropriate way. Semantics provide two predefined actions that are displayed in the context menu when you focus on an element. These actions are:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nfun SemanticsPropertyReceiver.expand(\n    label: String? = null,\n    action: (() -&gt; Boolean)?\n)\n\nModifier.semantics { this.expand(label = &quot;Show full text&quot;) { expandNode() } }\n\nfun SemanticsPropertyReceiver.collapse(\n    label: String? = null,\n    action: (() -&gt; Boolean)?\n)\n\nModifier.semantics { this.collapse(label = &quot;Show collapsed text&quot;) { collapseNode() } }\n<\/pre><\/div>\n\n\n<p>I have come across many times functionality where I want to allow content to expand and collapse, but only for accessibility purposes, for example, a long agreement near a checkbox. Below is an example implementation:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\n@Composable\nfun ExpandableContentDescription(painter: Painter, text: String) {\n    var isExpanded by remember { mutableStateOf(false) }\n    Row(\n        modifier = Modifier.semantics {\n            contentDescription = if (isExpanded) LONG_TEXT else &quot;Short description&quot;\n            customActions = mutableStateListOf(\n                if (isExpanded) CustomAccessibilityAction(\n                    label = &quot;Read short text&quot;,\n                    action = { isExpanded = false\n                    true }\n                ) else CustomAccessibilityAction(\n                    label = &quot;Read full text&quot;,\n                    action = { isExpanded = true\n                    true }\n                )\n            )\n        }\n    ) {\n        Text(text = LONG_TEXT)\n        Checkbox(checked = isChecked, onCheckedChange = onCheckedChange)\n    }\n}\n<\/pre><\/div>\n\n\n<p>Below is a gif with a demonstration of a full and short contentDescription:<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img decoding=\"async\" width=\"320\" height=\"720\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/2-2.gif\" alt=\"\" class=\"wp-image-25856\"\/><\/figure>\n\n\n\n<div style=\"height:20px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Progress bars<\/strong><\/h3>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img decoding=\"async\" width=\"320\" height=\"720\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/3-1.gif\" alt=\"\" class=\"wp-image-25858\"\/><\/figure>\n\n\n\n<p>Semantics allows you to set progress bar range information with data like current value, range and optional steps.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nSemanticsProperties.ProgressBarRangeInfo\nclass ProgressBarRangeInfo(\n    val current: Float,\n    val range: ClosedFloatingPointRange&amp;lt;Float&gt;,\n    \/*@IntRange(from = 0)*\/\n    val steps: Int = 0\n)\n<\/pre><\/div>\n\n\n<p>In the case of a progress bar where the current progress or range is not known, indeterminate should be used. Loading animations should be used with indeterminate progress bars.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nModifier.semantics { progressBarRangeInfo = ProgressBarRangeInfo.Indeterminate }\n<\/pre><\/div>\n\n\n<p>After focusing on Progress Bars with appropriate semantics, TalkBack will signal changes in the current value with a sound and inform you when the current value reaches the end of the range.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Live regions<\/strong><\/h3>\n\n\n\n<p>Live regions allow you to specify the elements that the accessibility tool should automatically notify you of changes to an element. Live Regions provides two modes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>polite \u2013 which will wait for other messages to finish,<\/li>\n\n\n\n<li>assertive \u2013 which will stop other messages.<\/li>\n<\/ul>\n\n\n\n<p>Marking a node with a Live region mode provides information to the accessibility service, which should notify the user when the text or content description changes. Examples include pop-ups used on the screen or a countdown when some action occurs after the timer ends.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nModifier.semantics { liveRegion = LiveRegionMode.Polite }\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Text<\/strong><\/h3>\n\n\n\n<p>Text should be handled by compose in most cases when you use Text() function:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nModifier.semantics { this.text = AnnotatedString(&quot;example text&quot;) }\n<\/pre><\/div>\n\n\n<p>Texts representing headlines should be marked with appropriate semantic properties:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nSemanticsProperties.Heading\n<\/pre><\/div>\n\n\n<p>By calling method:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nModifier.semantics { heading() }\n<\/pre><\/div>\n\n\n<p>This allows accessibility readers (for example, talk back) to navigate from headline to headline.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Collection items and selectable groups<\/strong><\/h3>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img decoding=\"async\" width=\"320\" height=\"720\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/4-1.gif\" alt=\"\" class=\"wp-image-25860\"\/><\/figure>\n\n\n\n<p>The container for the collection should set the Info properties of the collection:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nModifier.semantics { collectionInfo = CollectionInfo(rowCount = 10, columnCount = 10) },\n<\/pre><\/div>\n\n\n<p>Accessibility services should announce to user that focus enters collection. Elements in collection should set<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nModifier.semantics {\n    collectionItemInfo =\n        CollectionItemInfo(rowIndex = 2, rowSpan = 1, columnIndex = 2, columnSpan = 0)\n}\n<\/pre><\/div>\n\n\n<p>Accessibility services should inform the user that the element is located in the collection.<br>There is an automated approach to counting elements, rather than just having a developer manually provide them:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nSemanticsProperties.SelectableGroup\n<\/pre><\/div>\n\n\n<p>Selectable groups will be announced based on child items number. Note that for Lazy containers a number of elements won&#8217;t be available.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Read order<\/strong><\/h3>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img decoding=\"async\" width=\"320\" height=\"720\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/5-1.gif\" alt=\"\" class=\"wp-image-25862\"\/><\/figure>\n\n\n\n<p>There is a way to suggest the Ui focus path directly to the accessibility services. This can be performed by using a traversal group and setting custom traversal on child items:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nSemanticsProperties.IsTraversalGroup\n<\/pre><\/div>\n\n\n<p>For child items:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nSemanticsProperties.TraversalIndex\n<\/pre><\/div>\n\n\n<p>Should be set. Example of usage:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nRow(\n    modifier = Modifier.semantics { isTraversalGroup }\n) {\n    Text(&quot; read order 3&quot;, modifier = Modifier.semantics { traversalIndex = 1f })\n    Text(&quot;read order 1&quot;, modifier = Modifier.semantics { traversalIndex = -1f })\n    Text(&quot;read order 2&quot;, modifier = Modifier.semantics { traversalIndex = 0f })\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Roles<\/strong><\/h3>\n\n\n\n<p>Roles provide accessibility services with information about specific types of Ui elements:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nModifier.semantics { role = Role.Button }\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Custom Actions<\/strong><\/h3>\n\n\n\n<p>Custom actions are a way to give accessibility services an additional way of control. These actions are available from the context menu. The user can activate them after focusing on an item. Below is an example of their use:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nModifier.semantics {\n    customActions =\n        mutableStateListOf(CustomAccessibilityAction(&quot;example action&quot;) {\n            \/\/ your action\n            true\n        })\n}\n<\/pre><\/div>\n\n\n<p>An example of custom action can be found in the Expandable Content Description section.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Error and Disabled state<\/strong><\/h3>\n\n\n\n<p>Error is a file that explains to the accessibility service the input error, for example, a text file where the user entered an invalid E-mail should be marked as an error with a message explaining the problem.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nModifier.semantics { error(&quot;Description of error&quot;) }\n<\/pre><\/div>\n\n\n<p>Disabled is a simple state that should allow the accessibility service to focus on a specific element and inform you when that element is disabled.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nModifier.semantics { disabled() }\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\"><strong>Accessibility tools<\/strong><\/h2>\n\n\n\n<p>Android provides a semantic tree for all range of accessibility tools. The most well-known is Talk back, but there is also switch access, which allows you to control devices with just two actions (next, select). When developing interfaces, it&#8217;s worth remembering that the keyboard and mouse are great control devices for some users. Using the mouse is mostly straightforward, especially when you don&#8217;t need the hover effect and focus only on clicks. The keyboard requires additional code, but can speed up navigation when using switches on the keyboard.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Testing semantics<\/strong><\/h2>\n\n\n\n<p>There are two approaches to automated testing:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>snapshot tests \u2013 these are limited and focus mainly on testing areas of focus with the text that will be delivered to accessibility services.<\/li>\n\n\n\n<li>instrumental tests \u2013 allow you to check a set of semantic properties.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Snapshot<\/strong><\/h3>\n\n\n\n<p>Snapshot tests are based on rendered images. Testing simply involves checking pixel-by-pixel the currently generated images with a \u201cgolden\u201d image. In these tests, accessibility elements can be added to generated images. Here is an example:<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img decoding=\"async\" width=\"465\" height=\"194\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/5-1.jpg\" alt=\"\" class=\"wp-image-25832\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/5-1.jpg 465w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/5-1-300x125.jpg 300w\" sizes=\"(max-width: 465px) 100vw, 465px\" \/><\/figure>\n\n\n\n<p><a href=\"https:\/\/github.com\/cashapp\/paparazzi\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >A great library for snapshot testing is Paparazzi<\/a>.<\/p>\n\n\n\n<p>Add the dependency to the project\u2019s build.gradle file:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\ndependencies {\n    classpath &#039;app.cash.paparazzi:paparazzi-gradle-plugin:1.3.1&#039;\n}\n<\/pre><\/div>\n\n\n<p>And use the plugin in the module\u2019s build.gradle file:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nplugins {\n    id &#039;app.cash.paparazzi&#039;\n}\n<\/pre><\/div>\n\n\n<p>Now, we can start creating snapshot tests. Create a class for the unit tests:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nclass ExampleUnitTest {\n\n    @Test\n    fun test() {    }\n}\n<\/pre><\/div>\n\n\n<p>Add a rule to initialize paparazzi:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\n@get:Rule\n    val paparazzi = Paparazzi(\n        deviceConfig = PIXEL_5,\n        theme = &quot;android:Theme.Material.Light.NoActionBar&quot;,\n        renderExtensions = setOf(AccessibilityRenderExtension())\n    )\n<\/pre><\/div>\n\n\n<p>And create a test function:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\n@Test\nfun test() {\n    paparazzi.snapshot {\n        Content()\n    }\n}\n<\/pre><\/div>\n\n\n<p>The above code will generate and compare with a golden snapshot of Content() composable.<br>To generate the golden snapshots run:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\n.\/gradlew recordPaparazziDebug\n<\/pre><\/div>\n\n\n<p>By default, this command creates snapshots in src\/test\/snapshots.<br>To compare the golden snapshots with the current version, use the command:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\n.\/gradlew verifyPaparazziDebug\n<\/pre><\/div>\n\n\n<p>The configuration parameter responsible for adding the semantics properties of the graphical representation is:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nrenderExtensions = setOf(\n    AccessibilityRenderExtension()\n)\n<\/pre><\/div>\n\n\n<p>However, this will only display a few semantic properties, such as text, contentDescription, action labels. For example, progressBarRangeInfo won&#8217;t be shown.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Instrumented<\/strong><\/h3>\n\n\n\n<p>Instrumented tests are tests performed on Android devices. Jetpac compose provides a library for testing compose. Let&#8217;s see an example:<br>Add a file to build.gradle module:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\n   androidTestImplementation(&quot;androidx.compose.ui:ui-test-junit4:$compose_ui_version&quot;)\n\n    debugImplementation(&quot;androidx.compose.ui:ui-test-manifest:$compose_ui_version&quot;)\n<\/pre><\/div>\n\n\n<p>Create a class for the instrumented test:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nclass MyComposeTest {\n\n    @get:Rule\n    val composeTestRule = createComposeRule()\n\n    @Test\n    fun myTest() {\n        \/\/ Start the app\n        composeTestRule.setContent {\n            Content()\n        }\n\n    }\n}\n<\/pre><\/div>\n\n\n<p>Then you can test any semantic property by creating a matcher for it. Below is an example for indeterminate progress bar information:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nfun hasIndeterminateProgressBarInfo() = SemanticsMatcher(&quot;Node is not indeterminate progress bar range info&quot;) {\n    it.config.getOrNull(SemanticsProperties.ProgressBarRangeInfo) == ProgressBarRangeInfo.Indeterminate\n}\n<\/pre><\/div>\n\n\n<p>And use it like:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\ncomposeTestRule.onNodeWithTag(&quot;Example tag&quot;).assert(hasIndeterminateProgressBarInfo())\n<\/pre><\/div>\n\n\n<p>Similarly, you can perform assertions on any semantic property, even a custom one.<\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;25821&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;5&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;5&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;11&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;5\\\/5 ( votes: 5)&quot;,&quot;size&quot;:&quot;18&quot;,&quot;title&quot;:&quot;Accessibility in Compose&quot;,&quot;width&quot;:&quot;139.5&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} ( {votes}: {count})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 139.5px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 14.4px;\">\n            5\/5 ( votes: 5)    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Accessibility tools, such as TalkBack, use a semantics tree to learn the hierarchy and parameters of elements. Therefore, developers have &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.pl\/blog\/en\/accessibility-in-compose\/\">Continued<\/a><\/p>\n","protected":false},"author":596,"featured_media":25953,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_editorskit_title_hidden":false,"_editorskit_reading_time":0,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","inline_featured_image":false,"footnotes":""},"categories":[1320],"tags":[2622,1843,1682,1633,1590],"class_list":["post-25821","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-hard-development","tag-digital-en","tag-semantic","tag-mobile-2","tag-accessibility-2","tag-tools"],"acf":[],"aioseo_notices":[],"republish_history":[],"featured_media_url":"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/12\/Accessibility-in-Compose.jpg","category_names":["Hard development"],"_links":{"self":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/25821"}],"collection":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/users\/596"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/comments?post=25821"}],"version-history":[{"count":2,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/25821\/revisions"}],"predecessor-version":[{"id":25952,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/25821\/revisions\/25952"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/media\/25953"}],"wp:attachment":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/media?parent=25821"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/categories?post=25821"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/tags?post=25821"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}