<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Mobile accessibility notes]]></title><description><![CDATA[Mobile accessibility notes]]></description><link>https://swipewalk-dev.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 31 Oct 2026 15:39:09 GMT</lastBuildDate><atom:link href="https://swipewalk-dev.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Seven native Android and iOS accessibility quirks that are easy to miss until you run the app]]></title><description><![CDATA[Seven things I found testing small native sample apps on emulators, Simulators and real phones, with what to do about each]]></description><link>https://swipewalk-dev.hashnode.dev/seven-native-android-and-ios-accessibility-quirks-that-are-easy-to-miss-until-you-run-the-app</link><guid isPermaLink="true">https://swipewalk-dev.hashnode.dev/seven-native-android-and-ios-accessibility-quirks-that-are-easy-to-miss-until-you-run-the-app</guid><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Accessibility]]></category><category><![CDATA[mobile]]></category><dc:creator><![CDATA[Swipewalk dev]]></dc:creator><pubDate>Tue, 27 Oct 2026 18:41:30 GMT</pubDate><content:encoded><![CDATA[<p>Some accessibility problems in native apps look fine in code review and only show up on a device: with TalkBack or VoiceOver on, the text size turned up, or the theme switched. Here are seven I ran into while testing small native sample apps (Android Views and Jetpack Compose, iOS UIKit and SwiftUI) on emulators, Simulators and real phones, with what to do about each.</p>
<h2>1. SwiftUI: <code>.accessibilityLabel()</code> replaces the visible text</h2>
<pre><code class="language-swift">Button("Pay") { pay() }
    .accessibilityLabel("Submit")
</code></pre>
<p>On screen the button says "Pay". Its accessibility label is "Submit", and when we dumped every attribute XCUITest exposes for that button, "Pay" appeared nowhere. Someone using voice control who says "Tap Pay" may not reach it, and a screen reader user hears a different word from the one sighted colleagues refer to. This is the case WCAG 2.5.3 Label in Name (Level A) was written for: the accessible name should contain the visible text.</p>
<p>In our UIKit sample the visible title stayed in the tree next to the label; in SwiftUI only the label did (iOS 26.5 Simulator). Keep the visible word in the label, and put extra context in a hint:</p>
<pre><code class="language-swift">Button("Pay") { pay() }
    .accessibilityHint("Pays the parking ticket")
</code></pre>
<h2>2. SF Symbols can come with a name, but maybe not the right one</h2>
<p>An image button using <code>UIImage(systemName: "magnifyingglass")</code> with no label set came out with the accessible name "Search" in our tests (an iOS 26.5 Simulator and an iPhone on iOS 27). We saw this for that one symbol only. It's a generic name for the symbol, not for what your button does. If the button opens a filter or searches something specific, say so:</p>
<pre><code class="language-swift">searchButton.accessibilityLabel = "Search tickets"
</code></pre>
<p>On Android, an <code>ImageButton</code> or a Compose <code>Icon</code> button without a description gets no default name from the platform:</p>
<pre><code class="language-kotlin">IconButton(onClick = onSearch) {
    Icon(Icons.Default.Search, contentDescription = "Search tickets")
}
</code></pre>
<h2>3. UIKit text doesn't follow the text size setting unless you ask it to</h2>
<p>SwiftUI text styles scale with Dynamic Type automatically. UIKit labels need two things, a text-style font and <code>adjustsFontForContentSizeCategory</code>, or they don't update when the text size setting changes. On our two small sample screens, Apple's accessibility audit flagged 3 elements with unsupported Dynamic Type in the UIKit version and 1 in the SwiftUI version. One of the UIKit ones was a text field whose font had simply never been set.</p>
<pre><code class="language-swift">label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true

// Custom font that still scales:
label.font = UIFontMetrics(forTextStyle: .body)
    .scaledFont(for: UIFont(name: "Avenir-Medium", size: 17)!)
label.adjustsFontForContentSizeCategory = true
</code></pre>
<p>Both versions need <code>adjustsFontForContentSizeCategory = true</code>.</p>
<p>In SwiftUI, <code>.custom(_:size:)</code> already scales with the body style; add <code>relativeTo:</code> to scale with a different style. Only <code>.custom(_:fixedSize:)</code> stays fixed, so watch for that one.</p>
<pre><code class="language-swift">Text("Payment history").font(.custom("Avenir-Medium", size: 17, relativeTo: .headline))
</code></pre>
<h2>4. Compose: a button made of several texts is read as all of them</h2>
<p>A clickable Compose element merges its children for TalkBack and reads them in order. In our sample, a button built from two <code>Text</code>s ("Pay" and "$25.00") was announced as "Pay, $, 25.00, Button": TalkBack read the amount in two parts. And when one child had a description that didn't match its visible text ("Pay" described as "Submit", followed by "now"), TalkBack said "Submit, now, Button" while the screen showed "Pay now". (Android 16 emulator with TalkBack 16.0.0, and a Pixel 4a on Android 13 with TalkBack 17.0.1.)</p>
<p>If the parts don't read well together, give the whole button one name that contains the visible words:</p>
<pre><code class="language-kotlin">Button(
    onClick = onPay,
    modifier = Modifier.semantics { contentDescription = "Pay $25.00" }
) {
    Row(Modifier.clearAndSetSemantics { }) {
        Text("Pay")
        Text("$25.00")
    }
}
</code></pre>
<p><code>clearAndSetSemantics</code> clears everything after it in the modifier chain, so put it on the content, not on the Button's own modifier, or the button loses its click action.</p>
<h2>5. Hard-coded backgrounds with system-styled controls</h2>
<p>In our UIKit sample the screen's background was hard-coded white, while a standard system-styled button (<code>UIButton.Configuration.gray()</code>) kept following the appearance setting. In dark appearance its title measured about 1.48:1 against the light gray behind it, well under the 4.5:1 WCAG 1.4.3 (Contrast (Minimum), AA) asks for normal-size text. In light appearance there was no issue (iOS Simulator). The likely cause: system-styled parts change with the appearance, the hard-coded background doesn't.</p>
<p>Don't mix a hard-coded background with system-styled controls. Use semantic colours throughout:</p>
<pre><code class="language-swift">view.backgroundColor = .systemBackground
label.textColor = .label
</code></pre>
<p>The same mismatch is possible on Android when <code>android:windowBackground</code> is fixed but text colours come from a DayNight theme. Check every screen in both light and dark mode.</p>
<h2>6. Android: the same screen got a pane title on Android 13 but not on Android 16</h2>
<p>TalkBack can announce a pane title when content changes. The same Views screen in our sample had a pane title on an Android 13 Pixel and none on the Android 16 emulator; we didn't find out why. (A pane title is separate from the window or activity title, which TalkBack can also announce.) So don't rely on it happening by itself. Give each activity a meaningful title, and set a pane title explicitly for fragments or panes that change within one window:</p>
<pre><code class="language-kotlin">ViewCompat.setAccessibilityPaneTitle(paneView, "Payment history")
</code></pre>
<pre><code class="language-kotlin">// Compose
Column(Modifier.semantics { paneTitle = "Payment history" }) { /* ... */ }
</code></pre>
<p>Then turn TalkBack on and listen when you move between screens.</p>
<h2>7. <code>android:labelFor</code> works for TalkBack, but not every tool sees it</h2>
<pre><code class="language-xml">&lt;TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Ticket number"
    android:labelFor="@id/ticket" /&gt;
&lt;EditText
    android:id="@+id/ticket"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" /&gt;
</code></pre>
<p>TalkBack reads this field as "Edit box for Ticket number" (Android 16 emulator, TalkBack 16.0.0). But some testing tools build on <code>uiautomator dump</code>, whose output has no attribute for that link, so they may report the field as unlabelled. If a scanner flags a field you labelled with <code>labelFor</code>, check it with TalkBack before changing anything. A <code>TextInputLayout</code> hint is another way to name the field:</p>
<pre><code class="language-xml">&lt;com.google.android.material.textfield.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="Ticket number"&gt;
    &lt;com.google.android.material.textfield.TextInputEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content" /&gt;
&lt;/com.google.android.material.textfield.TextInputLayout&gt;
</code></pre>
<h2>How to check your own app</h2>
<ul>
<li>Turn on TalkBack or VoiceOver and swipe through each screen. Does every control have a name that matches what's on screen? Do screens announce a title?</li>
<li>Turn the text size up to the largest setting while the app is open. Does the text grow? Is anything cut off or out of reach? (Before Android 14 the Settings maximum is usually below 200%; <code>adb shell settings put system font_scale 2.0</code> tests 200%, and <code>1.0</code> sets it back. From Android 14, large text grows non-linearly, so big headings grow less than body text by design.)</li>
<li>Switch between light and dark mode and look at contrast again.</li>
<li>Try voice control (Voice Access or Voice Control): can you activate buttons by saying what you see?</li>
</ul>
<p>Automated checks catch only some accessibility problems. Testing with the screen readers yourself, and with people who use them, is still what counts.</p>
<p>I found these while testing small native sample apps for accessibility. If you've hit other platform quirks like these, I'd like to hear about them in the comments.</p>
<p><em>This article was created with the help of AI. The findings come from tests I ran with AI tooling on Android emulators, iOS Simulators and real phones (a Pixel 4a and an iPhone SE), on small sample apps.</em></p>
]]></content:encoded></item><item><title><![CDATA[Eight .NET MAUI accessibility problems that are easy to miss until you run the app]]></title><description><![CDATA[Many accessibility problems in a .NET MAUI app are easy to miss in XAML. They show up on a phone, with a screen reader on, the text size turned up, or the…]]></description><link>https://swipewalk-dev.hashnode.dev/eight-net-maui-accessibility-problems-that-are-easy-to-miss-until-you-run-the-app</link><guid isPermaLink="true">https://swipewalk-dev.hashnode.dev/eight-net-maui-accessibility-problems-that-are-easy-to-miss-until-you-run-the-app</guid><category><![CDATA[dotnet]]></category><category><![CDATA[#maui]]></category><category><![CDATA[Accessibility]]></category><category><![CDATA[mobile]]></category><dc:creator><![CDATA[Swipewalk dev]]></dc:creator><pubDate>Mon, 19 Oct 2026 16:23:24 GMT</pubDate><content:encoded><![CDATA[<p>Many accessibility problems in a .NET MAUI app are easy to miss in XAML. They show up on a phone, with a screen reader on, the text size turned up, or the theme switched. Here are eight I kept running into while testing MAUI apps on Android emulators, iOS Simulators and real phones, with what to do about each.</p>
<h2>1. AutomationId is not a name</h2>
<p><code>AutomationId</code> becomes the Android resource id and the iOS <code>accessibilityIdentifier</code>. It's meant for UI tests, not as a name for people. A control that only has an <code>AutomationId</code> has no accessible name, and screen readers handle that unpredictably: in my tests on Android, TalkBack read one such image button aloud as "btnCancelPayment", its raw id. Neither silence nor a developer id tells anyone what the button does.</p>
<pre><code class="language-xml">&lt;!-- No accessible name --&gt;
&lt;ImageButton Source="cancel.png" AutomationId="btnCancelPayment" /&gt;

&lt;!-- A name for people, AutomationId kept for UI tests --&gt;
&lt;ImageButton Source="cancel.png"
             AutomationId="btnCancelPayment"
             SemanticProperties.Description="Cancel payment" /&gt;
</code></pre>
<p>And don't copy the id into the description either: "btnCancelPayment" read aloud is not a name.</p>
<p><strong>Update:</strong> this applies to buttons and images. For text fields, don't use <code>SemanticProperties.Description</code>: Microsoft's docs say it stops TalkBack actions working on an <code>Entry</code> or <code>Editor</code> on Android. Use a visible <code>Label</code> plus <code>Placeholder</code> instead (on Android, <code>SemanticProperties.Hint</code> on an Entry also works). Thanks to Mehdi Mohseni in the comments for pointing this out.</p>
<h2>2. Headings are almost never set</h2>
<p>Screen reader users jump between headings to find their way around a page. In MAUI nothing is a heading unless you say so:</p>
<pre><code class="language-xml">&lt;Label Text="Payment history" SemanticProperties.HeadingLevel="Level1" /&gt;
</code></pre>
<h2>3. Page.Title isn't an Android pane title</h2>
<p>On Android, <code>Page.Title</code> shows in the toolbar, but MAUI doesn't set the accessibility pane title from it, so the page has no pane title for screen readers. Android also has a separate window title that TalkBack can announce, so whether people actually hear a title when the page changes varies. Turn TalkBack on and listen when you navigate to a new page.</p>
<h2>4. Changing the font size on Android can send the user back to the first page</h2>
<p>In an app built from the default template, <code>MainActivity</code> doesn't list <code>FontScale</code> in its <code>ConfigurationChanges</code>. When the user changes the system font size, Android recreates the activity, and the template's <code>App.CreateWindow</code> builds a new <code>AppShell</code>. The app comes back on its first page, and whatever the user was doing is gone. (Not every app loses its place; it depends on how the app restores its state.)</p>
<p>Adding <code>ConfigChanges.FontScale</code> avoids the restart, but then the text keeps its old size until the app is relaunched, unless your app re-applies its fonts itself. So it isn't a fix on its own. Test this on your most important flows: change the font size halfway through and see where you land.</p>
<h2>5. On iOS, text may not resize until the app restarts</h2>
<p>Before <code>Microsoft.Maui.Controls</code> 10.0.100, MAUI apps on iOS didn't update text while running after the system text size changed, only after the app was terminated and relaunched. It was fixed in 10.0.100 (<a href="https://github.com/dotnet/maui/pull/34445">dotnet/maui#34445</a>).</p>
<p>The catch: when I created a project with <code>dotnet new maui</code> on SDK 10.0.300, it resolved <code>Microsoft.Maui.Controls</code> 10.0.20, which is before the fix. The template's package reference uses <code>$(MauiVersion)</code>, so set it explicitly, to 10.0.100 or later:</p>
<pre><code class="language-xml">&lt;PropertyGroup&gt;
  &lt;MauiVersion&gt;10.0.100&lt;/MauiVersion&gt;
&lt;/PropertyGroup&gt;
</code></pre>
<p>Then change the text size in Settings while your app is open and check it grows. (In my tests, Shell tab bar titles on iOS didn't grow with the text size even on the fixed version.)</p>
<h2>6. Large text can push controls off the screen</h2>
<p>At normal size a page fits. With the font scale at 200% on a small phone, the controls at the bottom get pushed below the edge. If the page has no <code>ScrollView</code>, there may be no way to reach them. I saw this on a Pixel 4a (Android 13, font scale set to 2.0 over adb, since Android 13's own setting stops at 130%): the bottom buttons were cut off, swiping did nothing, and they were missing from the accessibility tree altogether.</p>
<p>Wrap long pages in a <code>ScrollView</code>, and test large text on your smallest supported device, not just the emulator.</p>
<h2>7. Check the template's text colours</h2>
<p>In the template styles of the MAUI version I tested, a page title came out light grey on white, around 1.7:1 contrast. WCAG 2.2's 1.4.3 Contrast (Minimum) asks for at least 4.5:1 for normal text and 3:1 for large text, so it falls short either way. Because it comes from the template, it's worth checking in your own app's styles.</p>
<h2>8. Test both themes</h2>
<p>Microsoft's WeatherTwentyOne sample app is a good example of why. When I scanned its first screen in dark mode, automated contrast checks found nothing. In light mode, on both an Android emulator and a Pixel 4a, the same screen had five text elements measured below WCAG 1.4.3's contrast minimum (around 2.1:1 to 2.2:1, under even the 3:1 large-text minimum). Same app, same screen, opposite results. If your app follows the system theme, check both.</p>
<h2>How to check your own app</h2>
<p>None of these need special tools:</p>
<ul>
<li><p>Turn on TalkBack (Android) or VoiceOver (iOS) and swipe through each screen. Does every control have a sensible name? Do pages announce a title? Can you reach everything?</p>
</li>
<li><p>Set the system text size to the largest setting while the app is open. Does the text grow? Do you stay on the same page? Is anything cut off or unreachable? Before Android 14 the largest Settings option is usually below the 200% WCAG 1.4.4 refers to (130% on our Pixel 4a); you can test 200% with <code>adb shell settings put system font_scale 2.0</code> (and set it back with <code>1.0</code>). On iOS, turn on Larger Accessibility Sizes.</p>
</li>
<li><p>Switch between light and dark mode and look at the contrast again.</p>
</li>
<li><p>Rotate the device.</p>
</li>
</ul>
<p>Automated checks catch only some accessibility problems. Testing with the screen readers yourself, and with people who use them, is still what counts.</p>
<p>I found these while testing MAUI apps for accessibility. If you've run into other MAUI accessibility quirks, I'd like to hear about them in the comments.</p>
<p><em>This article was created with the help of AI. The findings come from my own tests on Android emulators, iOS Simulators and real phones (a Pixel 4a and an iPhone SE).</em></p>
]]></content:encoded></item></channel></rss>