Accessible Kotlin: Writing Code That Doesn't Abandon Users

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why accessibility in Kotlin is a superpower, not a checkbox

\n\n

Imagine: you've written a cool app in Kotlin. Beautiful Material Design, smooth animations, reactive streams. You're proud of the code. But then it turns out that a person with low vision can't press the "Buy" button because TalkBack reads it as "unlabeled button". Or a user with hand tremors misses the tiny switch.

\n\n

Accessibility is not boring bureaucracy. It's about making your Kotlin code work for everyone. According to WHO, over a billion people have a disability. Ignoring them means losing an audience and, frankly, being a bad developer.

\n\n

In this article, we'll break down how to write accessible Kotlin code without pain. Without tons of boilerplate. With live examples you can copy and paste right now.

\n\n

Basic principles: ContentDescription is your best friend

\n\n

The most common accessibility failure is the lack of descriptions for UI elements. The TalkBack system (screen reader on Android) can't read images or icons. It needs text.

\n\n

In Kotlin, this is done via the contentDescription attribute. But not just "Button", but meaningfully.

\n\n

Bad example (don't do this)

\n
imageView.contentDescription = "Button"
\n\n

Good example

\n
binding.fabAddTask.contentDescription = "Add new task"
\n\n

If the icon is decorative (just an ornament) — set contentDescription = null so TalkBack ignores it.

\n\n
// Decorative icon — don't distract the user\nbinding.decorativeIcon.contentDescription = null
\n\n

Tip: Use string resources (strings.xml) to support localization. Don't hardcode Russian text in Kotlin files.

\n\n

Semantic markup: telling the system what matters

\n\n

Standard Views (Button, TextView) handle things well, but custom components often break accessibility. In Kotlin, we can explicitly set semantics.

\n\n

Adding a role to an element

\n
binding.customView.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES\nbinding.customView.accessibilityDelegate = object : View.AccessibilityDelegate() {\n    override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {\n        super.onInitializeAccessibilityNodeInfo(host, info)\n        info.text = "Current value: ${viewModel.value}"\n        info.contentDescription = "Volume slider"\n    }\n}
\n\n

This code tells the system: "Hey, this isn't just a square, it's a slider!". TalkBack will understand that the element is interactive and can interact correctly.

\n\n

Grouping elements

\n

If you have a product card with an image, price, and name — combine them into one focus. Otherwise, the user will listen to three separate elements.

\n\n
binding.cardProduct.apply {\n    isFocusable = true\n    contentDescription = "Product: ${product.name}, price: ${product.price} rubles"\n}
\n\n

Now TalkBack reads all the information in one click.

\n\n

Action navigation: keyboard and D-pad

\n\n

Not all users tap the screen with their finger. Some use a keyboard (via OTG) or D-pad (remote control). Your app should be controllable without touch.

\n\n

In Kotlin, this is solved via focusable and focus order (nextFocusDown, nextFocusForward).

\n\n
binding.buttonNext.nextFocusForwardId = R.id.buttonConfirm\nbinding.buttonConfirm.nextFocusDownId = R.id.buttonCancel
\n\n

Make sure all interactive elements receive focus. A common mistake is a TextView with ClickableSpan (a link in text). By default, it doesn't get focus. Fix it:

\n\n
binding.textWithLink.movementMethod = LinkMovement                

Blogs

Book Recommendations