🔡 Text spacing

Common mistake

A common mistake is hard-coding line height, letter spacing, and word spacing so that the values cannot be overridden by the user or the operating system. When spacing is too tight, readability suffers for users with dyslexia, cognitive disabilities, or low vision, and overriding it at the system level causes layout breaks or clipped text.

🎯 Relevant elements

This guideline applies to every widget that renders text:

  • Body text and headings (Text, RichText)
  • Button labels and form field text
  • List item titles and subtitles
  • Any custom widget that sets TextStyle properties explicitly

¡

WCAG Guideline

This guideline is based on WCAG 2.2 - 1.4.12 Text Spacing (Level AA). No loss of content or functionality must occur when the following spacing overrides are applied by the user:

Property Minimum value
Line height 1.5× the font size
Paragraph spacing 2× the font size
Letter spacing 0.12× the font size
Word spacing 0.16× the font size

Solution

Avoid fixed, non-scalable spacing values. Prefer Flutter’s default text layout, which responds to TextStyle properties, and test layouts at the minimum spacing values above.

// ❌ Don't - hard-coded pixel values for letter and word spacing that cannot scale
Text(
  'Hello, world!',
  style: const TextStyle(
    fontSize: 16,
    letterSpacing: 0.5,  // absolute px - does not scale with font size
    wordSpacing: 1.0,
    height: 1.2,          // too tight (< 1.5)
  ),
);

// ✅ Do - express spacing relative to the font size
const double fontSize = 16;
Text(
  'Hello, world!',
  style: const TextStyle(
    fontSize: fontSize,
    letterSpacing: 0.12 * fontSize,  // 0.12× font size
    wordSpacing: 0.16 * fontSize,    // 0.16× font size
    height: 1.5,                      // 1.5× font size (line height)
  ),
);

For paragraph spacing, ensure that spacing between adjacent Text blocks or Padding is at least twice the font size:

// ✅ Do - paragraph gap of at least 2× the body font size
Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: const [
    Text('First paragraph…', style: TextStyle(fontSize: 16)),
    SizedBox(height: 32), // 2 × 16 = 32
    Text('Second paragraph…', style: TextStyle(fontSize: 16)),
  ],
);

Validation & Testing

See the Validation & Testing setup guide for tool configuration.

Manual review

Use a test build that applies the WCAG 1.4.12 minimum spacing overrides globally and walk through every screen. Verify that no text is clipped, hidden behind other elements, or causes layout overflow.

// Inject maximum spacing for manual testing
Text(
  content,
  style: const TextStyle(
    fontSize: 16,
    height: 1.5,
    letterSpacing: 1.92,  // 0.12 × 16
    wordSpacing: 2.56,    // 0.16 × 16
  ),
);

TalkBack & VoiceOver

Verify that all text is still readable and fully announced after applying the spacing overrides.


flutter_test

testWidgets('body text renders without overflow at WCAG 1.4.12 spacing', (tester) async {
  tester.view.physicalSize = const Size(390, 844);
  tester.view.devicePixelRatio = 1.0;

  await tester.pumpWidget(
    MaterialApp(
      home: Scaffold(
        body: Text(
          'Sample body text that should not overflow.',
          style: const TextStyle(
            fontSize: 16,
            height: 1.5,
            letterSpacing: 1.92,  // 0.12 × 16
            wordSpacing: 2.56,    // 0.16 × 16
          ),
        ),
      ),
    ),
  );

  expect(tester.takeException(), isNull);
});

🛠️ Usage baseflow-a11y-components library

🛠️ Work in progress…


This site uses Just the Docs, a documentation theme for Jekyll.