Launch Club

Restricting Text Size in your Flutter Apps

Flutter

Published Nov 22, 2023

Marcus Ng

Marcus Ng

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

Dynamic Text iOS settings

Some apps break completely as text starts overflowing and running together, while others restrict the minimum and maximum text size to prevent these issues.

App with broken text

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'),
),

Flutter and Dart

made simple.

Everything you need to build production ready apps.

No spam. Just updates. Unsubscribe whenever.