Dynamic Type Contract

Status: ready

Intent

Critical flows stay readable and completable at accessibility text sizes, up to AX5.

Problem

The default mockup passes. At a larger size, content is clipped, the primary button disappears, or text overlaps.

Solution

Verdict: text styles for all text, no fixed heights on anything that contains text, a vertical layout at accessibility sizes, and scrolling on every screen with a primary action. Larger text is a required state of the design, not a QA edge case.

Why: at AX5, body text is about 53 pt instead of 17 pt. A layout designed only at 17 pt can't hold that by adjusting a few constraints.

Delivery text is truncated inside a fixed-height card and fully visible inside a flexible-height card.
The same string twice. Top: fixed height, clipped. Bottom: flexible height. Captured on iPhone 17 Pro Max, iOS 27.0.1.

Implementation

Text styles. Custom fonts relative to a style:

Text(paymentError).font(.callout)
Text(total).font(.custom("Inter-SemiBold", size: 17, relativeTo: .body))

Spacing and icons next to text scale with it:

@ScaledMetric(relativeTo: .body) private var badgePadding = 8

Horizontal at default sizes, vertical at accessibility sizes:

@Environment(\.dynamicTypeSize) private var size

var body: some View {
    let layout = size.isAccessibilitySize
        ? AnyLayout(VStackLayout(alignment: .leading))
        : AnyLayout(HStackLayout())
    layout {
        title
        action
    }
}

AnyLayout keeps view identity when the size changes, so state and focus survive.

Previews with real content, at AX3 and AX5:

#Preview("Checkout AX5") {
    CheckoutView(error: "Payment failed. Check the expiration date and try again.")
        .environment(\.dynamicTypeSize, .accessibility5)
}

A short mock string says nothing. Use real errors, long names and the longest localization.

UIKit:

label.font = UIFontMetrics(forTextStyle: .body)
    .scaledFont(for: UIFont(name: "Inter-SemiBold", size: 17)!)
label.adjustsFontForContentSizeCategory = true
label.numberOfLines = 0

Check

Profile:   Larger Text AX3 and AX5, on a device
Task:      checkout, account recovery, support form
Expected:  no truncated text in fields, prices, errors or buttons
           primary action reachable by scrolling
           each error stays next to its field
Profile:   German or Russian, AX3
Task:      the same flow
Expected:  button verbs not truncated, rows grow, nothing overlaps

Pitfalls

  1. Approving only short English strings.
  2. Fixed heights on rows with text.
  3. .minimumScaleFactor as the main fix. It shrinks text back down for the user who asked for it larger.
  4. Testing Profile and skipping Checkout.
  5. The keyboard covering the only recovery action.

WCAG mapping

  1. 1.4.4 Resize Text: text resizes to 200% without loss of content or function.
  2. 1.4.10 Reflow: content reflows instead of scrolling in two directions.
  1. Clipped Large Text
  1. Chapter 2
  2. Chapter 6
  3. Chapter 10
  4. Chapter 11
  5. Chapter 13

Sources

  1. Apple Human Interface Guidelines: Typography
  2. Apple Developer: DynamicTypeSize
  3. Apple Developer: ScaledMetric
  4. Apple Developer: UIFontMetrics
  5. App Store Connect Help: Larger Text evaluation criteria