Go to Settings > Accessibility > Display & Text Size on your iPhone. Drag the text size slider all the way to the right. Now open your app.
If your text stays the same size, you have a problem. Not a theoretical one -- a practical one that affects a significant percentage of your users right now. Apple has reported that a meaningful share of iOS users have changed their default text size. Many are older adults. Some have low vision. Others just prefer larger text because they're reading on a small screen, or in bright sunlight, or while walking.
Dynamic Type is the system that makes this work. When a user adjusts their preferred text size, every app that supports Dynamic Type scales its text accordingly. The user sets their preference once, and it applies everywhere. It's one of the most impactful accessibility features iOS offers.
Unless your app hardcodes its font sizes. Then it does nothing.
The Problem: Hardcoded Font Sizes Ignore User Preferences
SwiftUI makes it trivially easy to set a specific font size:
struct ArticleView: View {
let article: Article
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text(article.title)
.font(.system(size: 24, weight: .bold))
Text(article.author)
.font(.system(size: 14, weight: .medium))
.foregroundColor(.secondary)
Text(article.body)
.font(.system(size: 16))
.lineSpacing(4)
}
.padding()
}
}
This looks fine on a developer's phone with default text settings. The hierarchy is clear: 24pt title, 14pt byline, 16pt body. Ship it.
But when a user with their text size set to the largest option opens this view, every Text element stays exactly the same size. The 16pt body text that's comfortable for the developer is uncomfortably small for this user. Their system preference -- the one they configured specifically because they need larger text -- is being ignored.
The same issue shows up in UIKit with UIFont.systemFont(ofSize:):
titleLabel.font = UIFont.boldSystemFont(ofSize: 24)
bodyLabel.font = UIFont.systemFont(ofSize: 16)
Both of these create fonts that are pinned to a specific point size. Dynamic Type can't touch them.
The Fix: Use Text Styles
SwiftUI has a built-in system for fonts that scale with Dynamic Type: text styles. Instead of specifying a point size, you specify a semantic role:
struct ArticleView: View {
let article: Article
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text(article.title)
.font(.title)
Text(article.author)
.font(.subheadline)
.foregroundColor(.secondary)
Text(article.body)
.font(.body)
.lineSpacing(4)
}
.padding()
}
}
Now the title uses .title, which is large and bold at the default size but grows larger when the user increases their text size. The body uses .body, which is the standard reading size and scales proportionally. The subheadline sits between them in the hierarchy.
The available text styles in SwiftUI are: .largeTitle, .title, .title2, .title3, .headline, .subheadline, .body, .callout, .footnote, and .caption. Each one maps to a specific size at each Dynamic Type setting, and Apple has already worked out the scaling ratios so the visual hierarchy stays intact at every size.
What About Custom Fonts?
If your app uses a custom typeface, you can still support Dynamic Type. Use the relativeTo parameter:
Text("Welcome back")
.font(.custom("Avenir-Medium", size: 16, relativeTo: .body))
This tells SwiftUI: "Use Avenir Medium, start at 16pt, but scale it the same way .body scales." When the user increases their text size, this font grows proportionally. Without relativeTo, it stays at 16pt forever.
In UIKit, the equivalent is UIFontMetrics:
let customFont = UIFont(name: "Avenir-Medium", size: 16)!
titleLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: customFont)
titleLabel.adjustsFontForContentSizeCategory = true
Note the adjustsFontForContentSizeCategory = true -- in UIKit, this is required for the label to respond to real-time text size changes (when the user adjusts the setting while your app is in the foreground).
Why This Matters
WCAG 2.1 Success Criterion 1.4.4 (Resize Text) requires that text can be resized up to 200% without loss of content or functionality. This is Level AA -- not an aspirational target, but a standard expectation for accessible software. When you hardcode font sizes, you fail this criterion entirely because the text cannot be resized at all through user preferences.
Beyond compliance, fixed fonts create a frustrating experience. A user who has explicitly told their phone "I need larger text" is encountering your app and finding that their preference is being ignored. It signals that you either don't know about their needs or don't care. Neither is a message you want to send.
The fix is also one of the easiest accessibility wins in iOS development. Replacing .system(size: 16) with .body is a one-line change. There's no layout complexity, no design trade-off, no performance cost. It just works.
How AccessLint Catches This
AccessLint includes the A11Y.SWIFTUI.FIXED_FONT rule for SwiftUI and A11Y.UIKIT.FIXED_FONT_DYNAMIC_TYPE for UIKit. These rules detect font declarations that use explicit point sizes without Dynamic Type scaling.
Specifically, the SwiftUI rule flags:
.font(.system(size: 16))-- system font with a fixed size.font(.custom("Avenir", size: 16))-- custom font withoutrelativeTo:- Any
.font()call with asize:orfixedSize:parameter and norelativeTo:
It does not flag .font(.body), .font(.title), or .font(.custom("Avenir", size: 16, relativeTo: .body)) -- those all scale correctly.
Run it on the hardcoded example from earlier:
$ accesslint analyze --path ./Sources
AccessLint Analysis Complete
============================
Files analyzed: 23
Rules run: 25
Duration: 0.38s
Findings: 3
[MINOR] Minor: 3
Top issues:
[MINOR] [A11Y.SWIFTUI.FIXED_FONT] Fixed font size detected
Sources/Views/ArticleView.swift:7
[MINOR] [A11Y.SWIFTUI.FIXED_FONT] Fixed font size detected
Sources/Views/ArticleView.swift:10
[MINOR] [A11Y.SWIFTUI.FIXED_FONT] Fixed font size detected
Sources/Views/ArticleView.swift:14
If you use the --format xcode flag (for Xcode build phase integration), each finding shows as an inline warning:
Sources/Views/ArticleView.swift:7:17: warning: [A11Y.SWIFTUI.FIXED_FONT] Fixed font size detected: Use .system(.body) or another text style for Dynamic Type support
Sources/Views/ArticleView.swift:10:17: warning: [A11Y.SWIFTUI.FIXED_FONT] Fixed font size detected: Use .system(.body) or another text style for Dynamic Type support
Sources/Views/ArticleView.swift:14:17: warning: [A11Y.SWIFTUI.FIXED_FONT] Fixed font size detected: Use .system(.body) or another text style for Dynamic Type support
Each finding points to the exact line, explains the issue, and suggests the fix. The severity is minor by default -- it won't fail your CI unless you configure it to -- but you can upgrade it to major in your .accesslintrc.json if Dynamic Type support is a priority for your team:
{
"preset": "wcag-aa",
"rules": {
"A11Y.SWIFTUI.FIXED_FONT": { "severity": "major" }
}
}
What About Intentionally Fixed Sizes?
Sometimes you genuinely need a fixed font size. A logo wordmark that should stay at exactly 48pt. A numeric display in a calculator. A decorative element where scaling would break the layout.
AccessLint supports inline suppression for these cases:
// accesslint:disable A11Y.SWIFTUI.FIXED_FONT
Text("ACME")
.font(.system(size: 48, weight: .black))
// accesslint:enable A11Y.SWIFTUI.FIXED_FONT
The suppression is scoped to the specific rule and the specific lines. Everything else in the file still gets checked. This is the right pattern: be intentional about exceptions rather than turning off the check entirely.
Put It in Your Pipeline
Add AccessLint to your CI and every pull request gets scanned for fixed fonts along with the rest of the accessibility rules:
name: Accessibility
on:
pull_request:
paths: ['**/*.swift']
jobs:
accesslint:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: SyncTek-LLC/AccessLint@v1
with:
path: ./Sources
fail-on: major
Developers get immediate feedback. Fixed fonts get flagged in the same PR where they were introduced. No more discovering Dynamic Type issues months later in an accessibility audit.
Try It
AccessLint scans your SwiftUI and UIKit code for accessibility issues -- missing labels, fixed fonts, undersized touch targets, and more. Twenty-five rules, all running in seconds, no simulator required.
Install it and run your first scan:
$ brew tap synctek-llc/accesslint && brew install accesslint
$ accesslint analyze --path ./Sources
Full documentation and pricing at accesslint.app.
Ready to try AccessLint?
Install the CLI and catch your first accessibility violation in under two minutes. Free tier includes 10 rules.
Get Started Free