🔡 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
TextStyleproperties 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…