Skip to main content

Accessibility

We've reached the last stop in Advanced Topics, coming off Theming — and it's a fitting one to end on: making sure everything you've built so far is actually usable by everyone.

Worth saying up front: making a truly accessible app is genuinely complex, and touches on far more than any one page can cover. The Web Content Accessibility Guidelines are the place to go for the full picture on the web side of things. What follows is what Doodle itself gives you to work with.

Module Required

Include the AccessibilityModule (Web, Desktop) in your application to use these features.

package accessibility.module import io.nacular.doodle.application.Modules.Companion.AccessibilityModule import io.nacular.doodle.application.application import org.kodein.di.instance import rendering.MyApp fun main() { //sampleStart application(modules = listOf(AccessibilityModule)) { MyApp(instance(), instance()) } //sampleEnd }
package accessibility import io.nacular.doodle.application.Application import io.nacular.doodle.application.Modules.Companion.AccessibilityModule import io.nacular.doodle.application.application //sampleStart class MyApp(/*...*/): Application { init { // use accessibility features } override fun shutdown() {} } fun main () { // Include the AccessibilityModule to enable features application(modules = listOf(AccessibilityModule)) { MyApp(/*...*/) } } //sampleEnd

Descriptive Text​

accessibilityLabel​

Some Views just don't have meaningful text of their own — an icon-only button, say. accessibilityLabel fills that gap: short, descriptive text that assistive technologies announce when the View is selected.

package accessibility import io.nacular.doodle.controls.buttons.PushButton //sampleStart val button = PushButton("x").apply { accessibilityLabel = "Close the window" } //sampleEnd

accessibilityLabelProvider​

Often, though, the label already exists somewhere else in your UI — a Label sitting next to a text field, for instance. Rather than duplicating that text, point accessibilityLabelProvider at the View that already says it:

package accessibility import io.nacular.doodle.controls.text.Label import io.nacular.doodle.controls.text.TextField //sampleStart val label = Label("Enter your name") val textField = TextField().apply { accessibilityLabelProvider = label } //sampleEnd
info

You can link Views this way at any time, even before either one is actually displayed. Doodle tracks the relationship regardless, and surfaces it to assistive technologies the moment both Views are on screen together.

accessibilityDescriptionProvider​

Sometimes a short label isn't enough. accessibilityDescriptionProvider works exactly like accessibilityLabelProvider above — pointing to another View — but it's meant for longer, more detailed text describing what the View actually is.

Accessibility Roles​

Beyond labels and descriptions, a View can declare what it actually is — a button, a checkbox, a slider — by tagging it with an accessibility role. That lets assistive technologies adapt how they present the View to the user as they navigate through your app. You set this through the View's accessibilityRole.

Here's a View adopting the ButtonRole:

package accessibility import io.nacular.doodle.accessibility.ButtonRole import io.nacular.doodle.core.View //sampleStart class CustomButton: View(accessibilityRole = ButtonRole()) { // ... } //sampleEnd

That alone is enough — screen readers and other assistive technologies will now treat this View as a proper button. The button role has no extra state to manage, so adopting it is the whole job.

Some roles aren't quite so simple, though — they carry their own state, which needs to stay in sync with the View itself:

package accessibility import io.nacular.doodle.accessibility.ToggleButtonRole import io.nacular.doodle.core.View import io.nacular.doodle.utils.observable //sampleStart class CustomToggle(private val role: ToggleButtonRole = ToggleButtonRole()): View(accessibilityRole = role) { var selected by observable(false) { old, new -> role.pressed = new } } //sampleEnd

The good news: you'll rarely need to wire this up by hand. Most widgets in the Controls library ship with accessibility support already built in (and it keeps improving), often binding a role directly to the model underneath a control. ValueSlider ties its role straight to its ConfinedValueModel, for instance, so the two can never drift out of sync.

Recap​

That wraps up Advanced Topics. Next up: the Component Library, starting with Theming — the one idea nearly every control in it depends on.