Skip to content

accessibility overview, more iOS details #423

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion mpd.tree
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<toc-element toc-title="RTL languages" topic="compose-rtl.md"/>
<toc-element topic="compose-localization-tests.md"/>
</toc-element>
<toc-element topic="compose-accessibility.md"/>
<toc-element toc-title="iOS-specific features">
<toc-element toc-title="Integration with SwiftUI" accepts-web-file-names="compose-ios-ui-integration.html" topic="compose-swiftui-integration.md"/>
<toc-element toc-title="Integration with UIKit" topic="compose-uikit-integration.md"/>
Expand All @@ -69,7 +70,8 @@
<toc-element topic="compose-desktop-swing-interoperability.md"/>
</toc-element>
<toc-element toc-title="Compose Multiplatform for web" href="https://kotlinlang.org/docs/wasm-get-started.html"/>
<toc-element topic="compose-compiler.md"/>
<toc-element topic="compose-compiler.md">
</toc-element>
<toc-element topic="compose-compatibility-and-versioning.md"/>
<toc-element toc-title="Releases" href="https://github.com/JetBrains/compose-multiplatform/blob/master/CHANGELOG.md"/>
</toc-element>
Expand Down
109 changes: 109 additions & 0 deletions topics/compose/compose-accessibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
[//]: # (title: Accessibility)

Compose Multiplatform provides features essential for meeting accessibility standards, such as semantic properties,
accessibility APIs, and support for assistive technologies, including screen readers and keyboard navigation.

The framework enables the design of applications that comply with the requirements of the
[European Accessibility Act](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32019L0882) (EAA)
and the [Web Content Accessibility Guidelines](https://www.w3.org/TR/WCAG21/) (WCAG).

## Semantic properties

Semantic properties define the meaning and role of a component, adding context for services such as accessibility,
autofill, and testing.

Semantic properties are the building blocks of the semantic tree. When you define semantic properties in composables,
they are automatically added to the semantic tree. When assistive technologies interact with the app, they traverse
the semantic tree, not the entire UI tree.

Key semantic properties for accessibility:

* `contentDescription` provides a description for non-textual or ambiguous UI elements like
[`IconButton`](https://kotlinlang.org/api/compose-multiplatform/material3/androidx.compose.material3/-icon-button.html),
and [`FloatingActionButton`](https://kotlinlang.org/api/compose-multiplatform/material3/androidx.compose.material3/-floating-action-button.html).
It is the primary accessibility API and serves for providing a textual label that screen readers announce.

```kotlin
Modifier.semantics { contentDescription = "Description of the image" }
```

* `role` informs accessibility services about the UI component's functional type, such as button,
checkbox, or image. This helps screen readers interpret interaction models and announce the element properly.

```kotlin
Modifier.semantics { role = Role.Button }
```

* `stateDescription` describes the current state of an interactive UI element.

```kotlin
Modifier.semantics { stateDescription = if (isChecked) "Checked" else "Unchecked" }
```

* `testTag` is used primarily in UI testing via accessibility identifiers, like in the Espresso
framework on Android or XCUITest on iOS. In addition, `testTag` can be useful for debugging or in specific
automation scenarios where a component identifier is required.

```kotlin
Modifier.testTag("my_unique_element_id")
```

For a full list of semantics properties, see the
[SemanticsProperties API](https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/SemanticsProperties)
reference.

## Traversal order

By default, screen readers navigate through UI elements in a fixed order, following their layout from left
to right and top to bottom. However, for complex layouts, screen readers may not automatically determine the correct
reading order. This is crucial for layouts with container views,
such as tables and nested views, that support the scrolling and zooming of contained views.

To ensure the correct reading order when scrolling and swiping through complex views, define traversal semantic properties.
This also ensures correct navigation between different traversal groups with the swipe-up
or swipe-down accessibility gestures.

The default value of the traversal index is `0f`.
The lower the traversal index value of a component, the earlier its content description will be read relative
to other components.

For example, if you want the screen reader to prioritize a floating action button,
you can set its traversal index to `-1f`:

```kotlin
@Composable
fun FloatingBox() {
Box(
modifier =
Modifier.semantics {
isTraversalGroup = true
// Sets a negative index to prioritize over elements with the default index
traversalIndex = -1f
}
) {
FloatingActionButton(onClick = {}) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Icon of floating action button"
)
}
}
}
```

## What’s next

Learn more about accessibility features for iOS:

* [High-contrast theme](compose-ios-accessibility.md#high-contrast-theme)
* [Test accessibility with XCTest framework](compose-ios-accessibility.md#test-accessibility-with-xctest-framework)
* [Control via trackpad and keyboard](compose-ios-accessibility.md#control-via-trackpad-and-keyboard)
* [Synchronize the semantic tree with the iOS accessibility tree](compose-ios-accessibility.md#choose-the-tree-synchronization-option)
(for Compose Multiplatform 1.7.3 and earlier)

Learn more about accessibility features for desktop:

* [Enable accessibility on Windows](compose-desktop-accessibility.md#enabling-accessibility-on-windows)
* [Test your app with macOS and Windows tools](compose-desktop-accessibility.md#example-custom-button-with-semantic-rules)

For implementation details, see the [Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/accessibility).
4 changes: 1 addition & 3 deletions topics/compose/compose-rtl.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,7 @@ You can also define traversal semantics to ensure correct navigation between dif
with the swipe-up or swipe-down accessibility gestures.

For details on how to define traversal semantics and set traversal indexes,
refer to the [Accessibility](whats-new-compose-180.md#accessibility-for-container-views) section.

[//]: # (todo: replace accessibility link)
refer to the [Accessibility](compose-accessibility.md#traversal-order) section.

## Known issues

Expand Down
157 changes: 148 additions & 9 deletions topics/compose/ios/compose-ios-accessibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

Compose Multiplatform accessibility support allows people with disabilities to interact with the Compose Multiplatform UI
as comfortably as with the native iOS UI:

* Screen readers and VoiceOver can access the content of the Compose Multiplatform UI.
* The Compose Multiplatform UI supports the same gestures as the native iOS UI for navigation and interaction.

Expand All @@ -10,12 +11,131 @@ that are consumed by iOS Accessibility Services. For most interfaces built with
automatically.

You can also use this semantic data in testing and other automation: properties such as `testTag` will correctly map
to native accessibility properties such as `accessibilityIdentifier`. This makes semantic data from Compose Multiplatform available
to Accessibility Services and XCTest framework.
to native accessibility properties such as `accessibilityIdentifier`. This makes semantic data from Compose Multiplatform
available to Accessibility Services and XCTest framework.

## High-contrast theme

Compose Multiplatform uses the [`ColorScheme`](https://kotlinlang.org/api/compose-multiplatform/material3/androidx.compose.material3/-color-scheme/)
class from the material3 library, which currently lacks out-of-the-box support for high-contrast colours.
For a high-contrast theme on iOS, you need to add an extra set of colours
to the application palette. For each custom colour, its high-contrast version should be specified manually.

When you provide an additional high-contrast color palette, users need to manually select it, as it will not be
automatically triggered by iOS's system-level accessibility settings.

While defining a color palette, use a WCAG-compliant contrast checker tool to verify that your chosen `onPrimary` color has
sufficient contrast with your primary color, `onSurface` with a surface color, and so on.
Ensure the contrast ratio between colors is at least 4.5:1. For custom foreground and background colors,
the contrast ratio should be 7:1, especially for small text. This applies to both your `lightColorScheme`
and `darkColorScheme`.

This code shows how to define high-contrast color palettes for light and dark themes:

```kotlin
import androidx.compose.ui.graphics.Color

// Defines a data class to hold the color palette for high-contrast themes
data class HighContrastColors(
val primary: Color, // Main interactive elements, primary text, top app bars
val onPrimary: Color, // Content displayed on top of a 'primary' color
val secondary: Color, // Secondary interactive elements, floating action buttons
val onSecondary: Color, // Content displayed on top of a 'secondary' color
val tertiary: Color, // An optional third accent color
val onTertiary: Color, // Content displayed on top of a 'tertiary' color
val background: Color, // Main background of the screen
val onBackground: Color, // Content displayed on top of a 'background' color
val surface: Color, // Card backgrounds, sheets, menus, elevated surfaces
val onSurface: Color, // Content displayed on top of a 'surface' color
val error: Color, // Error states and messages
val onError: Color, // Content displayed on top of an 'error' color
val success: Color, // Success states and messages
val onSuccess: Color, // Content displayed on top of a 'success' color
val warning: Color, // Warning states and messages
val onWarning: Color, // Content displayed on top of a 'warning' color
val outline: Color, // Borders, dividers, disabled states
val scrim: Color // Dimming background content behind modals/sheets
)

// Neutral colors
val Black = Color(0xFF000000)
val White = Color(0xFFFFFFFF)
val DarkGrey = Color(0xFF1A1A1A)
val MediumGrey = Color(0xFF888888)
val LightGrey = Color(0xFFE5E5E5)

// Primary accent colors
val RoyalBlue = Color(0xFF0056B3)
val SkyBlue = Color(0xFF007AFF)

// Secondary and tertiary accent colors
val EmeraldGreen = Color(0xFF28A745)
val GoldenYellow = Color(0xFFFFC107)
val DeepPurple = Color(0xFF6F42C1)

// Status colors
val ErrorRed = Color(0xFFD32F2F)
val SuccessGreen = Color(0xFF388E3C)
val WarningOrange = Color(0xFFF57C00)

// Light high-contrast palette, dark content on light backgrounds
val LightHighContrastPalette =
HighContrastColors(
primary = RoyalBlue,
onPrimary = White,
secondary = EmeraldGreen,
onSecondary = White,
tertiary = DeepPurple,
onTertiary = White,
background = White,
onBackground = Black,
surface = LightGrey,
onSurface = DarkGrey,
error = ErrorRed,
onError = White,
success = SuccessGreen,
onSuccess = White,
warning = WarningOrange,
onWarning = White,
outline = MediumGrey,
scrim = Black.copy(alpha = 0.6f)
)

// Dark high-contrast palette, light content on dark backgrounds
val DarkHighContrastPalette =
HighContrastColors(
primary = SkyBlue,
onPrimary = Black,
secondary = EmeraldGreen,
onSecondary = White,
tertiary = GoldenYellow,
onTertiary = Black,
background = Black,
onBackground = White,
surface = DarkGrey,
onSurface = LightGrey,
error = ErrorRed,
onError = White,
success = SuccessGreen,
onSuccess = White,
warning = WarningOrange,
onWarning = White,
outline = MediumGrey,
scrim = Black.copy(alpha = 0.6f)
)
```
{initial-collapse-state="collapsed" collapsible="true" collapsed-title="val LightHighContrastPalette = HighContrastColors( primary = RoyalBlue,"}

iOS accessibility support is in the early stages of development. If you have trouble with this feature,
we would appreciate your feedback in the [#compose-ios](https://kotlinlang.slack.com/archives/C0346LWVBJ4/p1678888063176359)
Slack channel or as an issue in [YouTrack](https://youtrack.jetbrains.com/newIssue?project=CMP).
## Control via trackpad and keyboard

Compose Multiplatform for iOS supports additional input methods to control your device. Instead of relying on the
touchscreen, you can enable either AssistiveTouch to use a mouse or trackpad, or Full Keyboard Access to use a keyboard:

* AssistiveTouch (**Settings** | **Accessibility** | **Touch** | **AssistiveTouch**) allows you to control your
iPhone or iPad with a pointer from a connected mouse or trackpad. You can use the pointer to click icons on your
screen, navigate through the AssistiveTouch menu, or type using the onscreen keyboard.
* Full Keyboard Access (**Settings** | **Accessibility** | **Keyboards** | **Full Keyboard Access**) enables device
control with a connected keyboard. You can navigate with keys like **Tab** and activate items using **Space**.

## Customize synchronization of the accessibility tree

Expand Down Expand Up @@ -94,9 +214,28 @@ ComposeUIViewController(configure = {
}
```

## Test accessibility with XCTest framework

You can use the semantic accessibility data in testing and other automation. Properties such as `testTag` correctly map
to native accessibility properties such as `accessibilityIdentifier`. This makes semantic data from Compose Multiplatform
available to Accessibility Services and the XCTest framework.

You can use automated accessibility audits in your UI tests.
Calling `performAccessibilityAudit()` for your `XCUIApplication` will audit the current view for accessibility
issues just as the Accessibility Inspector does.

```swift
func testAccessibilityTabView() throws {
let app = XCUIApplication()
app.launch()
app.tabBars.buttons["MyLabel"].tap()

try app.performAccessibilityAudit()
}
```

## What's next?

Now that you're up to speed with iOS accessibility support in Compose Multiplatform:
* Try out the project generated by [the Kotlin Multiplatform wizard](https://kmp.jetbrains.com/) in your usual iOS accessibility workflow.
* Learn about [resource qualifiers](compose-multiplatform-resources.md) that will be helpful when adapting
a Compose Multiplatform UI to a particular situation.
* Learn more in the [Apple accessibility](https://developer.apple.com/accessibility/) guide.
* Try out the project generated by [the Kotlin Multiplatform wizard](https://kmp.jetbrains.com/) in your usual
iOS accessibility workflow.
36 changes: 3 additions & 33 deletions topics/whats-new/whats-new-compose-180.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,47 +271,16 @@ Localized versions of these announcements are also provided, allowing VoiceOver

#### Accessibility for container views

By default, screen readers navigate through UI elements in a fixed order,
following their layout from left to right and top to bottom.
For complex layouts,
screen readers cannot determine the correct reading order without properly defined traversal semantics.
This is particularly relevant for layouts with container views, such as tables and nested views,
that support the scrolling and zooming of contained views.

Starting with Compose Multiplatform %composeVersion%,
you can define traversal semantic properties for containers
to ensure the correct reading order when scrolling and swiping through complex views.

For example, if you want the screen reader to first focus on a floating action button,
you can set its traversal index to `-1f`.
The default value of the index is `0f`,
which means that the lower specified value causes the element to be read before others on the screen.

```kotlin
@Composable
fun FloatingBox() {
Box(
modifier =
Modifier.semantics {
isTraversalGroup = true
// Sets a negative index to prioritize over elements with the default index
traversalIndex = -1f
}
) {
FloatingActionButton(onClick = {}) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Icon of floating action button"
)
}
}
}
```

In addition to proper sorting elements for screen readers,
support for traversal properties enables navigation between different traversal groups with the swipe-up or swipe-down accessibility gestures.
To switch to accessible navigation mode for containers, rotate two fingers on the screen while VoiceOver is active.

Learn more about traversal semantic properties in the [Accessibility](compose-accessibility.md#traversal-order) section.

#### Accessible text input

In Compose Multiplatform %composeVersion% we've introduced support for text fields' accessibility traits.
Expand All @@ -321,6 +290,7 @@ ensuring proper accessibility-state representation.
You can now also use accessible text input in UI testing.

#### Support for control via trackpad and keyboard

Compose Multiplatform for iOS now supports two additional input methods to control your device. Instead of relying on the touchscreen,
you can enable either AssistiveTouch to use a mouse or trackpad, or Full Keyboard Access to use a keyboard:

Expand Down