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.
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
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
AccessibilityModule(Web, Desktop) switches these features on, same as every other opt-in module you've met so far.accessibilityLabelgives a View text when it has none of its own;accessibilityLabelProvider/accessibilityDescriptionProviderpoint to an existing View instead of duplicating its text.accessibilityRoletells assistive technologies what a View actually is — some roles need their state kept in sync, most built-in controls handle that for you already.
That wraps up Advanced Topics. Next up: the Component Library, starting with Theming — the one idea nearly every control in it depends on.