In Flutter, what do TextScaler.clamp and MediaQuery.withClampedTextScaling do, and when is clamping the user's text scale justified?
answer
- a range of scaled sizes
- minScaleFactor and maxScaleFactor
- withClampedTextScaling wraps a subtree
- min equal to max means linear
- local cap, never app-wide
basics
~10 sTextScaler.clamp limits scaled text to between minScaleFactor and maxScaleFactor times the font size; MediaQuery.withClampedTextScaling applies that to a subtree. Clamp only locally, such as oversized display text, never the whole app's body text.
solid answer
~40 s`textScaler.clamp(minScaleFactor: 1.0, maxScaleFactor: 1.5)` returns a scaler whose output stays within `[1.0 * fontSize, 1.5 * fontSize]`; both parameters default to no limit. `MediaQuery.withClampedTextScaling(maxScaleFactor: 1.5, child: ...)` wraps a subtree in a `MediaQuery` carrying the clamped scaler, so every `Text` below it obeys; if min equals max the result is `TextScaler.linear(min)`. `MediaQuery.withNoTextScaling` turns scaling off. Clamping is justified for text that is already large or decorative — a 48-pixel departure clock on a train timetable, a platform number inside a fixed-size badge — where the information is legible anyway. Clamping at the app root, for example in `MaterialApp.builder`, takes the setting away from exactly the users who need it; fix the layout instead.
code
dart · 24 linesimport 'package:flutter/material.dart';
class NextDeparture extends StatelessWidget {
const NextDeparture({super.key, required this.time, required this.destination});
final String time;
final String destination;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Already 48 logical pixels: cap only this display text.
MediaQuery.withClampedTextScaling(
maxScaleFactor: 1.3,
child: Text(time, style: const TextStyle(fontSize: 48)),
),
// Body text keeps the user's full setting.
Text(destination, style: Theme.of(context).textTheme.bodyLarge),
],
);
}
}go deeper
Recall that clamp and withClampedTextScaling cap how far text grows, and that the cap belongs on specific widgets, not the app.
Explain the minScaleFactor and maxScaleFactor range, how a subtree MediaQuery applies it, and the min equals max case.
Argue case by case which text may be capped, and replace root-level clamps with layout fixes that survive the largest setting.
Set a team rule that any text-scale clamp needs a written reason and a large-text screenshot, so caps do not spread silently.
## What clamping does A **`TextScaler`** maps an unscaled font size to a scaled one according to the user's accessibility setting. **Clamping** restricts that mapping to a range: - `TextScaler clamp({double minScaleFactor = 0, double maxScaleFactor = double.infinity})` returns a new scaler whose output lies in `[minScaleFactor * fontSize, maxScaleFactor * fontSize]`. - The defaults, `0` and `double.infinity`, mean no limit on that side. - Clamping keeps the underlying curve inside the range, so Android 14's nonlinear scaling is still honoured below the cap. ## Applying it to a subtree Widgets read the ambient scaler from `MediaQuery`, so the way to change what a whole subtree sees is to insert a new `MediaQuery`: | Helper | Effect on the subtree | |---|---| | `MediaQuery.withClampedTextScaling(minScaleFactor: ..., maxScaleFactor: ..., child: ...)` | the current scaler, clamped | | `MediaQuery.withNoTextScaling(child: ...)` | `TextScaler.noScaling` | | `Text(..., textScaler: ...)` | one widget only | `withClampedTextScaling` must sit below an existing `MediaQuery`, asserts that `maxScaleFactor >= minScaleFactor` and that `minScaleFactor` is finite and non-negative, and when the two are equal produces `TextScaler.linear(minScaleFactor)`. ## When clamping is justified Clamping is a trade: it protects a layout by giving part of the user's setting back. That is reasonable when the text is already easy to read: 1. **Display-size text.** On a train-timetable screen, the next departure time is shown at 48 logical pixels. At a 2x setting it would be 96 and push the platform list off screen; capping at `maxScaleFactor: 1.3` keeps it huge and on screen. 2. **Text in a fixed graphic.** A platform number inside a circular badge that must stay aligned with a track diagram. 3. **Content repeated elsewhere.** An abbreviated label whose full text appears, scaled, in the row below. It is not justified for: - **body text** such as the destination, calling points and delay notices; - **the whole app**, through a `MediaQuery` in `MaterialApp.builder`, which silently turns the user's accessibility setting into a smaller one everywhere; - **covering up a layout bug**, where rows have fixed heights or text cannot wrap. ## Why app-wide clamping is a defect Users raise the font size because they cannot read the default. A root-level `maxScaleFactor: 1.3` means someone who set 200% reads text at 130% in this app while every other app on the device honours the setting. The overflow it prevented is a layout problem, and the fix is in the layout: wrapping text, flexible rows, scrolling, and switching a `Row` to a `Column` when the scaled text no longer fits. ## Practical rules - Clamp **as low in the tree as possible**, around the one widget that needs it. - Prefer a **maximum only**; a minimum above 1.0 overrides users who chose smaller text. - Choose caps from the rendered size, not a round number: 48-pixel text at 1.3x is still far larger than 14-pixel body text at 2x. - Test the clamped widget at the largest setting to confirm the cap actually prevents the breakage.
- In Flutter, what does MediaQuery.withClampedTextScaling produce when minScaleFactor equals maxScaleFactor?It produces `TextScaler.linear(minScaleFactor)`: the range collapses to one value, so every font size is multiplied by that fixed factor regardless of the user's setting. Passing 1.0 for both is the same as switching scaling off, which is what `MediaQuery.withNoTextScaling` does more explicitly.
- Why is a minScaleFactor above 1.0 usually a mistake in Flutter?A minimum above 1.0 forces text larger than the design size even for users who chose a smaller system font, overriding their setting in the other direction. Clamping exists to protect layouts from extreme growth; a floor rarely protects anything and takes a choice away from users.
saying these in an interview costs you the question
- Clamping text scale in MaterialApp.builder is a standard best practice
- TextScaler.clamp turns nonlinear scaling into linear scaling
- withClampedTextScaling works without a MediaQuery ancestor
- Clamping body text to 1.3x is fine because 130% is already large
- clamp's parameters are required and have no defaults