Whenever I download a new app, I love seeing how it handles accessibility features like Dynamic Type.
“The Dynamic Type feature allows users to choose the size of textual content displayed on the screen. It helps users who need larger text for better readability. It also accomodates those who can read smaller text, allowing more information to appear on the screen.” - Apple Docs
Some apps break completely as text starts overflowing and running together, while others restrict the minimum and maximum text size to prevent these issues.
Ideally, we would want to support the entire range of dynamic text sizes, but that’s not realistic to do when starting out because it’s too time consuming. We would have to design how the app should look with a bunch of different text sizes for multiple screen sizes.
Here’s the widget I use in all of my apps to clamp the text scale factor to ensure that Dynamic Type doesn’t create an unusable user experience.
import 'package:flutter/material.dart';
/// {@template text_scale_factor_clamper}
/// Constrains text scale factor of app to certain range.
///
/// Wraps all widgets created under the [MaterialApp].
/// ```
/// MaterialApp(
/// builder: (_, child) => TextScaleFactorClamper(child: child!),
/// ...
/// ),
/// ```
/// {@endtemplate}
class TextScaleFactorClamper extends StatelessWidget {
/// {@macro text_scale_factor_clamper}
const TextScaleFactorClamper({
super.key,
required this.child,
this.minTextScaleFactor,
this.maxTextScaleFactor,
}) : assert(
minTextScaleFactor == null ||
maxTextScaleFactor == null ||
minTextScaleFactor <= maxTextScaleFactor,
'minTextScaleFactor must be less than maxTextScaleFactor',
),
assert(
maxTextScaleFactor == null ||
minTextScaleFactor == null ||
maxTextScaleFactor >= minTextScaleFactor,
'maxTextScaleFactor must be greater than minTextScaleFactor',
);
/// Child widget.
final Widget child;
/// Min text scale factor.
final double? minTextScaleFactor;
/// Max text scale factor.
final double? maxTextScaleFactor;
@override
Widget build(BuildContext context) {
final mediaQueryData = MediaQuery.of(context);
final constrainedTextScaleFactor = mediaQueryData.textScaler.clamp(
minScaleFactor: minTextScaleFactor ?? 1,
maxScaleFactor: maxTextScaleFactor ?? 1.3,
);
return MediaQuery(
data: mediaQueryData.copyWith(
textScaler: constrainedTextScaleFactor,
),
child: child,
);
}
}
If we want to constrain all text in our app to a specified range, all we have to do is use TextScaleFactorClamper
in the builder
of MaterialApp
. This defaults to a minScaleTextFactor
of 1
and a maxTextScaleFactor
of 1.3
.
MaterialApp(
builder: (_, child) => TextScaleFactorClamper(child: child!),
// ...
)
Pass in minScaleTextFactor
and maxScaleTextFactor
to change the scaling limits. Just make sure that maxScaleTextFactor
is greater than or equal to minScaleTextFactor
.
MaterialApp(
builder: (_, child) => TextScaleFactorClamper(
minScaleTextFactor: 0.75,
maxScaleTextFactor: 1.5,
child: child!,
),
// ...
)
If we want to disable scaling for certain widgets, then we can wrap those widgets directly in TextScaleFactorClamper
and pass in maxTextScaleFactor: 1
. This will override the text scale factor set in the MaterialApp
.
TextScaleFactorClamper(
maxTextScaleFactor: 1,
child: Text('Some unscalable text'),
),