Guide for Mobile Text Scaling
Mobile applications must conform to WCAG 1.4.4 Resize Text and scale content to ensure no loss of information or functionality for users requiring larger font sizes. This guide covers recommended ways to support text scaling on both iOS and Android platforms.
Platform-Specific Features
iOS - Dynamic Type
Dynamic Type is an accessibility feature within iOS that allows font sizes to be scaled device-wide based on the user's preference. Find this option under Accessibility Settings on the device. Read more from Apple on Text Display here.
Android - Text and Display Scaling
For Android devices 13 and up, you can change the preferred text and content size in accessibility settings. Users can adjust both font size and display size independently. Read more on changing these settings in Google's support guide for text and display settings.
Preparing Your Views
Ensure Content Can Scroll
iOS: When text is scaled to larger font sizes, substantial content could be pushed off-screen. Implement a UIScrollView
or ScrollView
on any screen with content.
Android: Use a ScrollView
element as the container for layout elements (constraint, linear, relative). ScrollViews will not scroll until the screen's height (or width, for horizontal scroll) is filled.
Exceptions:
- Android:
RecyclerViews
don't need to be embedded within aScrollView
. Allow items to expand vertically or horizontally. - Android: Navigation elements (bottom bars, tabs, toolbars) should be at the same level as the containing
ScrollView
, not within it.
Ensure Content Can Expand
iOS: Utilize contentHuggingPriority
, contentCompressionResistancePriority
, greaterThanOrEqualTo
, and lessThanOrEqualTo
to allow views to expand. Limit setting constant height and width for views containing content.
Android: Most text resizing issues can be resolved by not restricting the height or width of a view. Allow TextView
to expand with constraints set. Use wrap_content
for height and either match_parent
or 0dp
for width.
Layout-Specific Considerations
Android ConstraintLayout: Set view width to match_parent
and height to wrap_content
, or use 0dp
width to fill space between constraints. Always ensure components can expand vertically.
Android RelativeLayout: Define start and end guidelines for sibling elements to ensure components remain on-screen.
Android LinearLayout: Can adjust to changing content sizes if in a ScrollView
and don't set match_parent
height. Use android:minHeight
for specific height requirements.
Set Number of Lines
iOS: Set numberOfLines
property to 0 for any text with overflow potential. For UIButton
, set numberOfLines
to 0 on its titleLabel
.
Android: Use SP (scale-independent pixels) for all text, not DP (density-independent pixels). You can utilize Android Studio's accessibility linter to catch SP usage issues.
Implementation
iOS Implementation
SwiftUI Views (iOS 14+)
Using Default Fonts:
.font(.system(.largeTitle, design: .rounded))
Using Custom Fonts:
.font(.custom("FontName", size: 16, relativeTo: .body))
UIKit Views
Using Default Fonts:
label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true
Using Custom Fonts with UIFontMetrics:
guard let font = UIFont(name: "CustomFont", size: UIFont.labelFontSize) else { return }
label.font = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: font)
label.adjustsFontForContentSizeCategory = true
Manual Dynamic Type Handling:
// Via Notification Observer
NotificationCenter.default.addObserver(self,
selector: #selector(changeTextSize),
name: UIContentSizeCategory.didChangeNotification,
object: nil)
// Via TraitCollection Override
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
// Handle font size changes based on preferredContentSizeCategory
}
Android Implementation
Basic Setup:
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp" />
ConstraintLayout Example:
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button Text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
Programmatic Text Modification:
SpannableStringBuilder str = new SpannableStringBuilder(tv.getText());
str.setSpan(new AbsoluteSizeSpan(70, true), 15, 18, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(str);
Testing and Best Practices
General Guidelines
- Test against various font sizes to ensure content displays as expected.
- Keep headings short and descriptive for better accessibility.
- Ensure usability isn't affected by improper constraints.
- Verify all text controls support scaling without pushing content off-screen.
Platform-Specific Considerations
iOS:
- Follow Apple's Typography Guidelines and Supporting Dynamic Type documentation.
- Use appropriate TextStyles (
.body
,.title1
,.caption1
, etc.). - For SwiftUI, many considerations are provided by default but should be reviewed.
Android:
- Use short titles for toolbars; avoid Toolbar widget for dynamic titles.
- Provide distinct iconography with descriptive content descriptions for bottom navigation.
- Utilize scrollable views for tabs, ViewPager, and TabList components.
- Don't limit the height of controls.
Key Takeaways
- Always implement scrollable containers for content that might overflow.
- Use platform-appropriate scaling units (SP for Android, TextStyles for iOS).
- Test thoroughly across different font sizes and accessibility settings.
- Allow views to expand rather than constraining them to fixed dimensions.
- Follow platform guidelines for optimal user experience.