Dynamic Type and Text Scaling in Flutter: Adaptive Typography
Master dynamic type and text scaling in Flutter. Learn how to support user font preferences, handle text overflow, and build adaptive layouts.
Published on • September 19, 2026
AI Assistant

Dynamic Type and Text Scaling in Flutter: Adaptive Typography
Dynamic type — the ability for users to adjust text size system-wide — is a fundamental accessibility feature. Flutter fully supports text scaling through MediaQuery, allowing your app to adapt its typography to user preferences without any extra configuration.
Why Dynamic Type Matters
Over 30% of users adjust their device’s text size for better readability. Without proper support, scaled text can:
- Overflow containers and break layouts
- Become truncated or unreadable
- Overlap other elements and create confusion
Flutter’s text scaling support ensures your app remains usable at any text size.
How Text Scaling Works
Flutter applies the user’s text scale factor to all Text widgets by default. You can access this factor through MediaQuery:
final textScale = MediaQuery.textScaleFactorOf(context);
// Default: 1.0
// User scaled up: 1.2, 1.5, 2.0, etc.
// User scaled down: 0.8, 0.5, etc.
Basic Text Scaling
Automatic Scaling (Default Behavior)
// This text automatically scales with user preferences
Text(
'This text scales automatically',
style: const TextStyle(fontSize: 16),
)
Custom Scaling Behavior
Text(
'This text uses custom scaling',
style: TextStyle(
fontSize: 16 * MediaQuery.textScaleFactorOf(context).clamp(0.8, 1.4),
),
)
No Scaling (Fixed Size)
Builder(
builder: (context) {
final textScale = MediaQuery.textScaleFactorOf(context);
return Text(
'This text ignores scaling',
style: TextStyle(
fontSize: 16 / textScale, // Compensate for scaling
),
);
},
)
Handling Layout Challenges
Constrained Text with Overflow
Container(
constraints: const BoxConstraints(maxWidth: 200),
child: Text(
'This is a long text that might overflow when scaled up',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 16),
),
)
Flexible Layouts
LayoutBuilder(
builder: (context, constraints) {
final textScale = MediaQuery.textScaleFactorOf(context);
final fontSize = (16.0 * textScale).clamp(12.0, 24.0);
return Container(
padding: const EdgeInsets.all(16),
child: Text(
'Responsive text content',
style: TextStyle(fontSize: fontSize),
),
);
},
)
Auto-Sizing Text
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
'This text fits its container',
style: const TextStyle(fontSize: 24),
),
)
Advanced Patterns
Responsive Font Sizes
class ResponsiveText extends StatelessWidget {
final String text;
final double baseFontSize;
final double minFontSize;
final double maxFontSize;
const ResponsiveText({
super.key,
required this.text,
this.baseFontSize = 16,
this.minFontSize = 12,
this.maxFontSize = 24,
});
@override
Widget build(BuildContext context) {
final textScale = MediaQuery.textScaleFactorOf(context);
final fontSize = (baseFontSize * textScale).clamp(minFontSize, maxFontSize);
return Text(
text,
style: TextStyle(fontSize: fontSize),
);
}
}
Scaling with Constraints
class ScalableCard extends StatelessWidget {
final String title;
final String description;
const ScalableCard({
super.key,
required this.title,
required this.description,
});
@override
Widget build(BuildContext context) {
final textScale = MediaQuery.textScaleFactorOf(context);
final titleSize = (20.0 * textScale).clamp(16.0, 28.0);
final descSize = (14.0 * textScale).clamp(12.0, 20.0);
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: titleSize,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
description,
style: TextStyle(fontSize: descSize),
),
],
),
),
);
}
}
Text Scaling in Tables
DataTable(
columns: const [
DataColumn(label: Text('Name')),
DataColumn(label: Text('Value')),
DataColumn(label: Text('Status')),
],
rows: data.map((item) {
return DataRow(cells: [
DataCell(Text(item.name)),
DataCell(Text(item.value.toString())),
DataCell(Text(item.status)),
]);
}).toList(),
)
Testing Text Scaling
Visual Testing
testWidgets('handles text scaling', (tester) async {
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(textScaleFactor: 2.0),
child: MaterialApp(
home: Scaffold(
body: Text('Scaled text', style: TextStyle(fontSize: 16)),
),
),
),
);
// Verify text renders without overflow
expect(find.text('Scaled text'), findsOneWidget);
});
Golden Tests
testWidgets('text scaling golden test', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: MediaQuery(
data: const MediaQueryData(textScaleFactor: 1.5),
child: MyWidget(),
),
),
),
);
await expectLater(
find.byType(MyWidget),
matchesGoldenFile('golden/text_scaling_1.5.png'),
);
});
Best Practices
- Never set fixed heights on text containers — let them grow with scaled text
- Use
FittedBoxfor text that must fit its container - Test at multiple scale factors — 0.8, 1.0, 1.5, 2.0
- Provide sensible limits — clamp text size to prevent extreme scaling
- Use
maxLinesandoverflow— handle text that becomes too long - Consider layout impact — scaled text may push other elements
Platform-Specific Notes
iOS (Dynamic Type)
iOS supports Dynamic Type natively. Flutter respects the system setting:
- Settings > Display & Brightness > Text Size
Android (Font Size)
Android’s font size setting is reflected in MediaQuery.textScaleFactorOf:
- Settings > Display > Font size
Web
The browser’s zoom level affects text scaling. Flutter respects this through CSS:
@media (prefers-contrast: more) {
body {
font-size: 120%;
}
}
Resources
Dynamic type support is essential for building inclusive Flutter apps. By properly handling text scaling, you ensure your app remains readable and usable for all users, regardless of their visual needs.