-
Notifications
You must be signed in to change notification settings - Fork 2
Accessibility
Synced from docs/accessibility.md @ 7e68002. Edit there, not here.
Candela is built to be useful to people who read with their ears as much as their eyes — and that overlaps deeply with the audience for assistive technology. The accessibility work documented here is the result of a two-phase audit and refit that landed in v0.5.42 (Phase 1 scaffold) and v0.5.43 (Phase 2 behavior + static fixes).
This page is the canonical reference for what we support, what we know needs more work, and how to file an accessibility issue.
-
TalkBack — Android's built-in screen reader. We test against
uiautomator dumpproxies since the audit tablet (Samsung Tab A7 Lite, no Google Mobile Services) doesn't ship TalkBack itself. Issue #488 tracks installing TalkBack for live verification. -
Switch Access — Material 3 components participate in Switch
Access traversal by default. The
BottomTabBarexposesRole.Tab+selectedsemantics so Switch Access can announce the current tab. -
System font scaling — Compose's
.spunit honors the user's system-wide font scale setting. All user-facing text rides this scale. -
Reduced motion — when the user enables "Remove animations" in
Android Settings (which sets
Settings.Global.ANIMATOR_DURATION_SCALEto 0), Candela'sLocalReducedMotionCompositionLocal flips on and most decorative animations skip. The Phase 2 reduce-motion sweep (#480) wires this throughAnimatedVisibility/tween/LaunchedEffectsites that previously animated unconditionally. - High-contrast theme — opt-in via Settings → Accessibility → High contrast. Brass-on-near-black at roughly AAA contrast (14:1+ on body text). The default Library Nocturne palette stays warm; high contrast is offered as a switch, not a swap.
-
Accessibility subscreen — Settings → Accessibility surfaces seven
toggles in one place (font scale, high contrast, reduce motion, larger
touch targets, TalkBack hints, etc.) backed by DataStore preference
keys (
pref_a11y_*) so the user's choices persist across launches.
Phase 1 — scaffold (v0.5.42, #489)
- New Settings → Accessibility subscreen with toggles for the seven Phase 2 affordances.
-
AccessibilityStateBridgeFlow that observes the systemAccessibilityManager(TalkBack on/off, touch-exploration on/off, enabled-services list) and exposes it to Compose as a single state. - Seven
pref_a11y_*DataStore preference keys for persistence. - Notion plugin pinned first in the registry — the default Browse landing is now a curated reader-friendly home rather than the community-mirror grid.
Phase 2 — static labels + Roles (v0.5.43, #491)
Closes the high-leverage labeling gaps from the audit.
| Issue | Fix |
|---|---|
| #478 | 7 Switch composables wrapped in Modifier.toggleable(role = Role.Switch) so TalkBack announces the row's label merged with the switch state (was: unlabeled "Switch, on/off"). |
| #485 |
BottomTabBar.TabCell exposes Role.Tab + selected = isSelected so TalkBack can announce the current tab. |
| #484 | Two CircularProgressIndicator sites (sync OAuth handshake, fiction-detail epub export) tag themselves with contentDescription = "Loading: <label>" + liveRegion = Polite so users hear the spinner. |
| #481 | Sweep over 27 bare Modifier.clickable sites that lacked Role semantics. Each got a Role.Button / Role.Switch / Role.Checkbox + onClickLabel (action verb). |
| #483 | Hard-coded fontSize = N.sp literals rebound to typography ramp tokens where the size is theme-coupled. Dev-only surfaces (DebugOverlay / DebugScreen) kept their literals with kdoc explaining why. |
| #482 | Audit verification — see "Verified false positives" below. |
Wires the Settings → Accessibility toggles to actual rendering:
- High-contrast theme variant (
HighContrastLibraryNocturnepalette) - Reduced-motion consumer across the ~10 sites with unconditional animations
- Touch-target widener that bumps
min-targetfrom 48dp to 64dp when the user opts in - "Learn more" → this page (the row was a TODO in Phase 1).
Verified false positives — issue #482
The static audit flagged 64 Icon / Image / IconButton sites where
contentDescription was either null or missing. We verified each;
all but zero are correctly decorative. The breakdown:
-
36 sites with
contentDescription = null— every one of these is an icon that sits next to a descriptiveTextinside a parent that merges semantics (aSettingsRow,Card.clickable, etc.). Labeling the icon would create TalkBack noise ("Library, library, library"). This is the correct pattern per the Compose accessibility guidance. -
28 IconButton sites flagged as "MISSING" — these are
IconButton(onClick = ...)wrappers whose innerIcon(...)has its owncontentDescription. The audit grep matched on the IconButton signature alone but didn't follow into the body. All verified correctly labeled at the innerIcon.
No contentDescription = "" empty-string bugs (those would block
TalkBack from reading the element at all). null is correctly used
throughout to mark decoration.
Surfaces audited — issue #487
Rendered-surface audit on R83W80CAFZB (Samsung Galaxy Tab A7 Lite,
800x1340 @ 213dpi) across 15 screens via uiautomator dump:
| Surface | Total clickables | Unlabeled (pre-Phase-2) | Sub-48dp targets |
|---|---|---|---|
| Library home | 17 | 0 | 5 (filter chips at 47dp — rounding) |
| Browse | 15 | 0 | 8 (source-filter chips at 47dp) |
| Settings hub | 26 | 7 | 0 |
| Settings → Voice | 11 | 1 | 0 |
| Settings → Plugins | 22 | 4 | 0 |
| Settings → Accessibility | 7 | 0 | 0 |
| Voice library | 19 | 0 | 1 (favorite star at 36dp — fixed in #479) |
| Audiobook view | 14 | 0 | 0 |
| Voice quick sheet | 9 | 1 | 0 |
| Pronunciation dict | 6 | 1 | 0 |
| Fiction detail | 12 | 0 | 0 |
Total unlabeled clickables: 9, all of which trace to the Switch pattern fixed in #478. After Phase 2 these surfaces are clean.
-
TalkBack hints: the
traversalIndex/customActionsemantics on the audiobook transport controls would let TalkBack users skip past the cover art to the play button faster. Not in v0.5.43. - Live TalkBack verification: blocked on #488 (install Android Accessibility Suite on R83W80CAFZB).
-
Voice library row count: when the library is in a long state
(200+ voices), TalkBack reads each row sequentially. A
LazyListLayoutInfo-based "row 47 of 200" announcement would speed navigation. Tracked separately.
If TalkBack announces something confusing, a tap target is too small,
contrast feels low, or motion makes you queasy — file at
github.com/techempower-org/candela/issues
with the accessibility label and a screenshot or uiautomator dump
snippet if you can. We treat accessibility regressions as high priority.
We're looking to partner with screen-reader users and disability-rights
organizations for ongoing real-device testing. If you'd like to help —
either as a tester or as a connection to a community — reach the
maintainers at the GitHub issues link above with the partnership
label.
Start here
Using Candela
- Fiction sources
- Reading & library
- Export & extras
- Voices
- Voice catalog
- AI chat
- Cloud sync
- Accessibility
- Settings reference
- Troubleshooting
Developer
Project