iOS Typography System
Apple's San Francisco font family with Dynamic Type support for accessibility. Automatically switches between SF Pro Text for smaller sizes (≤19pt) and SF Pro Display for larger sizes (≥20pt). As of iOS 18, Dynamic Type supports 12 sizes total — 7 standard sizes (xSmall through xxxLarge) plus 5 Larger Accessibility Sizes (AX1–AX5) — and body text can scale up to approximately 310% at AX5. Apple HIG — Typography
iOS Default Typography Styles
Dynamic Type Demo
Implementation Guidelines for iOS
Here are the best practices to properly implement typography in iOS applications:
- Use San Francisco font for a native iOS look and feel
- Implement Dynamic Type for accessibility compliance — iOS 18 supports 7 standard sizes (xSmall, Small, Medium, Large, xLarge, xxLarge, xxxLarge) plus 5 Larger Accessibility Sizes (AX1–AX5), 12 total
- Set minimum font size for body text to 17pt for readability
- For custom fonts, use
UIFontMetricsfor Dynamic Type scaling - Test your app across all 12 Dynamic Type sizes — 7 standard (xSmall → xxxLarge) and 5 Larger Accessibility Sizes (AX1–AX5). Body text reaches approximately 310% at AX5 — design layouts to handle this
- Large Content Viewer (iOS 13+): For fixed-size UI elements that cannot grow with Dynamic Type — such as tab bars, toolbars, and navigation bar icons — implement the Large Content Viewer so users can still identify them at maximum text sizes. Use
showsLargeContentViewer = trueon UIKit elements or the.accessibilityShowsLargeContentViewer()modifier in SwiftUI
Apple Developer — Scaling Fonts Automatically
// Swift: Dynamic Type with system font
label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true
// Swift: Custom font with UIFontMetrics
let customFont = UIFont(name: "CustomFont-Regular", size: 16)!
label.font = UIFontMetrics.default.scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true
// SwiftUI: Restrict Dynamic Type range when layout requires it
// (only use .accessibility1–.accessibility5 if you've tested all sizes)
Text("Sample Text")
.font(.body)
.dynamicTypeSize(.large ... .accessibility5)
// SwiftUI: Large Content Viewer for fixed-size elements
Image(systemName: "star.fill")
.accessibilityShowsLargeContentViewer()
Android Typography System
Android uses the Roboto font family in Material Design 3. Text sizes in sp (scalable pixels) automatically scale with user preferences. As of Android 14, the maximum font scale increased from 130% to 200%, and the system now applies a non-linear scaling curve — larger text grows more slowly than smaller text to prevent oversized display text from breaking layouts. Android 14 — Font Scaling
Android Material Typography Styles
Font Scaling Demo
Implementation Guidelines for Android
Here are the best practices to properly implement typography in Android applications:
- Always define text sizes in scalable pixels (sp) to respect user font size preferences — never use
dporpxfor text - Also use sp for line heights — if you define
lineHeightindp, it will not scale with the font, causing text to overlap at larger sizes on Android 14+ - The minimum recommended size for body text is 16sp for readability (Material Design 3 minimum is 14sp)
- For dense lists or information, you can use 14sp with a slightly heavier weight
- Use dynamic layouts with
wrap_contentandConstraintLayoutto adapt to different text sizes - Android 14+: Test font scale from 0.85x up to 2.0x — the maximum increased from 130% to 200% in Android 14. The system applies non-linear scaling, so large Display text grows more slowly than Body text
- Avoid setting fixed heights on containers that hold text — use
wrap_contentorminHeightinstead
Android Developer — Screen Densities & sp units
<!-- XML: use sp for both textSize AND lineHeight -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:lineHeight="24sp"
android:textColor="@color/textPrimary"
android:text="@string/sample_text" />
<!-- Compose: MaterialTheme styles handle sp scaling automatically -->
Text(
text = "Sample Text",
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.fillMaxWidth()
)
<!-- Compose: Never hardcode sp values directly — use the type scale -->
<!-- MaterialTheme.typography.bodyLarge = 16sp / 24sp line height -->
Typography Accessibility Guidelines
Creating accessible typography following WCAG 2.2 AA standards for text scaling and readability. WCAG 2.2 was published October 2023 and is the current recommended standard. WCAG 2.2 Specification
WCAG 2.2 Success Criterion 1.4.3 — Contrast (Minimum)
Requirement
Level AAText and images of text must have a contrast ratio of at least 4.5:1 against their background. Large text (18pt / 14pt bold or larger) requires at least 3:1.
Typography-specific rules
- Body text (≤17pt regular, ≤13pt bold): minimum 4.5:1 contrast ratio
- Large text (≥18pt regular, or ≥14pt bold): minimum 3:1 contrast ratio
- Placeholder text is real text — it must also meet 4.5:1, not just be "visible"
- Disabled text is exempt, but use sparingly — if content is important enough to show, it should be readable
- Dynamic Type interaction: as text scales up it may cross the large text threshold (18pt), meaning its required contrast ratio drops from 4.5:1 to 3:1 — your design must pass at both ends of the scale
- WCAG 2.2 AAA raises the bar further to 7:1 (body) and 4.5:1 (large text) via SC 1.4.6 — worth targeting for high-stakes content
Mobile-specific considerations
- On iOS, the system Bold Text setting increases font weight — a text style that was body-weight may become bold, potentially crossing the large text threshold
- On Android, High Contrast Text mode forces system-level contrast adjustments — test that your custom text colors aren't overriding these
- Layered elements (text over images, text over gradients) must meet contrast at the worst point of the gradient — not the average
WCAG 2.2 Success Criterion 1.4.4 — Resize Text
Requirement
Level AAText can be resized without assistive technology up to 200% without loss of content or functionality.
Mobile Implementation
- Respect OS text size preferences
- Use Dynamic Type (iOS) and sp units (Android)
- Maintain functionality at all text sizes
- Avoid horizontal scrolling
WCAG 2.2 Success Criterion 1.4.10 — Reflow
Requirement
Level AAContent can be presented without loss of information or functionality, and without requiring scrolling in two dimensions at a viewport width equivalent to 320 CSS pixels.
What this means for mobile typography
- At 400% browser zoom (equivalent to a 320px-wide viewport), all text content must reflow into a single column — no horizontal scrolling
- Exception: content that requires two-dimensional layout for usage (data tables, maps, certain diagrams)
- On native mobile: design single-column layouts that handle the largest text sizes without overflow
- Avoid fixed-width containers with
overflow: hidden— text will be clipped rather than reflowing
WCAG 2.2 Success Criterion 1.4.12 — Text Spacing
Requirement
Level AANo loss of content or functionality occurs when users set all of the following: line height to at least 1.5× the font size, paragraph spacing to at least 2× the font size, letter spacing to at least 0.12× the font size, and word spacing to at least 0.16× the font size.
What this means for mobile typography
- Your layouts must not break when users apply custom text spacing via accessibility settings or browser extensions
- Avoid fixed-height containers that clip text when line height is increased
- iOS Bold Text setting increases font weight without affecting spacing — test this separately
- On Android, the system does not expose direct letter-spacing or word-spacing overrides, but your code should not hard-code tight spacing that would conflict with OS-level adjustments
WCAG 2.2 Success Criterion 2.5.8 — Target Size (Minimum)
Requirement
Level AA — New in WCAG 2.2The size of the target for pointer inputs is at least 24×24 CSS pixels, except where spacing is sufficient or the target is inline text.
Why this matters for typography
- At large text sizes, button labels wrap or shrink — the visual label may remain readable but the tap target can fall below 24×24px
- iOS HIG recommends 44×44pt minimum touch targets — significantly stricter than WCAG's minimum
- Android Material Design 3 recommends 48×48dp minimum touch targets
- Use
minHeight/minWidthconstraints rather than wrapping content tightly - Test tap targets at AX5 (iOS) and 2.0x font scale (Android) — this is when they are most likely to fail
Assistive Technology vs. Accessibility Features
✅ Acceptable (Accessibility Features)
- iOS: Dynamic Type size adjustments in Settings
- Android: Font size scaling in Display settings
- Both: Bold text system preferences
- WebViews: Built-in browser zoom functionality
⚠️ Not Sufficient Alone (Assistive Technology)
- iOS: Magnifier or 3-finger zoom
- Android: Magnification gestures
- Both: External screen reader zoom features
Note: While these are valuable, they should supplement, not replace, proper text scaling support.
Implementation Requirements for WCAG 1.4.4 Compliance
1. Implement Native Text Scaling
Use platform-specific APIs to ensure text scales with system settings:
// iOS - UIKit
label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true
// iOS - SwiftUI
Text("Sample Text")
.font(.body)
.dynamicTypeSize(.large ... .accessibility5)
<!-- Android XML -->
<TextView
android:textSize="16sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
// Android Compose
Text(
text = "Sample Text",
style = MaterialTheme.typography.bodyLarge
)
2. Responsive Layout Design
Ensure layouts adapt to larger text without breaking:
// iOS - Auto Layout
stackView.axis = .vertical
stackView.spacing = 8
label.numberOfLines = 0
// SwiftUI
VStack(spacing: 8) {
Text("Title")
.lineLimit(nil)
}
<!-- Android ConstraintLayout -->
<androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintWidth_percent="0.8" />
</androidx.constraintlayout.widget.ConstraintLayout>
3. Handle Dynamic Changes
Respond to font size changes at runtime:
// iOS - Notification handling
NotificationCenter.default.addObserver(
self,
selector: #selector(contentSizeCategoryDidChange),
name: UIContentSizeCategory.didChangeNotification,
object: nil
)
// Android - Configuration changes
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// Refresh UI to reflect new font scale
recreateViews()
}
Understanding Non-Linear Text Scaling
Both iOS and Android use non-linear scaling curves, meaning different text styles scale at different rates to maintain readability.
| Text Style | Default (Large) | AX5 (approx.) | Scale Factor (approx.) | Reaches 200%? |
|---|---|---|---|---|
| Large Title | 34pt | ~76pt | ~2.24x (~224%) | ✅ Yes |
| Title 1 | 28pt | ~60pt | ~2.14x (~214%) | ✅ Yes |
| Body | 17pt | ~53pt | ~3.12x (~312%) | ✅ Yes |
| Caption 1 | 12pt | ~38pt | ~3.17x (~317%) | ✅ Yes |
| Base Size | Default (1.0x) | At 2.0x setting | Actual Scale Factor | Reaches 200%? |
|---|---|---|---|---|
| 8sp (small label) | 8sp | ~17sp | ~2.1x (210%) | ✅ Yes |
| 12sp (caption) | 12sp | ~21sp | ~1.75x (175%) | ❌ No |
| 14sp (body medium) | 14sp | ~23sp | ~1.64x (164%) | ❌ No |
| 16sp (body large) | 16sp | ~25sp | ~1.56x (156%) | ❌ No |
| 24sp (headline) | 24sp | ~34sp | ~1.42x (142%) | ❌ No |
| 57sp (display large) | 57sp | ~64sp | ~1.12x (112%) | ❌ No |
Key Insight — Android 14 Non-Linear Curve
Android 14's FontScaleConverter applies an intentionally non-linear curve: small text scales aggressively (8sp can exceed 200%) while large display text barely grows (57sp only reaches ~112% at the 2.0x setting). This prevents display-size text from becoming unusably large while ensuring small text remains readable. Only very small text reaches the 200% WCAG threshold — but this is still considered compliant because the platform provides the maximum available scaling, and the curve is designed to optimise legibility across the full type scale.
Testing for WCAG 1.4.4 Compliance
Complete Testing Process
Step 1: Platform Setup
📱 iOS Setup
- Open Settings → Accessibility
- Tap Display & Text Size
- Select Larger Text
- Enable Larger Accessibility Sizes
- Test all sizes from xSmall to AX5
🤖 Android Setup
- Open Settings → Display
- Tap Font size and style
- Test all sizes (Small to Huge)
- For extreme testing, use ADB:
adb shell settings put system font_scale 2.0
Step 2: Validation Criteria
✅ Pass Requirements
- All text scales using OS accessibility features
- Content remains accessible at all sizes
- No functionality lost when text enlarged
- No horizontal scrolling required
- Interactive elements remain usable
Step 3: Common Issues Checklist
🔍 Layout Issues
- Fixed containers: Text overflow in fixed-height containers
- Overlapping elements: Scaled text overlaps other UI elements
- Cut-off text: Text clipped in constrained spaces
✂️ Truncation Issues
Most large text failures come from truncation that was never designed for scale. Watch for:
- line-clamp / lineLimit: Hard caps that silently hide content at large sizes — use
nil/0for unlimited lines unless layout genuinely requires it - ellipsize / truncationMode: Ellipsis hides content rather than reflowing — prefer scrollable containers or adaptive layouts
- Fixed heights:
frame(height:)in SwiftUI orlayout_height="48dp"clips text — usewrap_contentor no fixed height - Fallback rule: If truncation is unavoidable, put the most important words first — they survive the cut. Never truncate at the start or middle
🎯 Interaction Issues
- Button labels: Action buttons become unusable if text doesn't fit
- Navigation: Tab bars and menus lose functionality
- Form inputs: Labels and inputs misalign or overlap
Step 4: Documentation
- 📋 Record test results for each text size
- 📸 Screenshot any layout issues
- 📝 Document workarounds for identified problems
- ✅ Create compliance checklist for future releases
Testing Checklist
iOS Testing
Android Testing
Best Practices
Design
- Plan for 200%+ text scaling
- Use minimum 16sp/17pt for body text
- Ensure WCAG AA color contrast
- Design flexible layouts
Development
- Implement Dynamic Type/sp units
- Use flexible layout systems
- Test with max accessibility settings
- Handle font size notifications
Testing
- Test with real users
- Use accessibility testing tools
- Verify all functionality works
- Document any limitations
W3C MATF Recommendations Implementation
The W3C Mobile Accessibility Task Force (MATF) has been working to clarify how WCAG applies to native mobile apps. WCAG 2.2 was published October 2023 and is the current recommended standard, superseding WCAG 2.1. The MATF publishes WCAG2Mobile (W3C Group Note, updated May 2025), which explains how WCAG 2.2 applies to native, web, and hybrid mobile apps. It is informative guidance only — not a normative W3C standard — but it is the most current official mobile accessibility reference from W3C:
- Assistive Technology vs Accessibility Features: Distinguishes between zoom and magnification (assistive technologies) and system text scaling features.
- Non-linear text scaling: Documents real scaling behavior on iOS and Android to help teams test typography under extreme settings.
- Platform-specific implementation guidance: Explains how platform APIs such as Dynamic Type and sp units support accessible text scaling.
- WebView considerations: Highlights that WCAG success criteria apply directly to web content rendered within native apps.
- Testing methodologies: Provides practical testing steps for validating text scaling and layout resilience across accessibility settings.
Last reviewed against WCAG 2.2 (October 2023), iOS 18, and Android 14. WCAG2Mobile is a W3C Group Note — informative guidance only, not a normative standard.