Skip to content

[web] Listen to text spacing overrides#172915

Closed
Renzo-Olivares wants to merge 114 commits into
flutter:masterfrom
Renzo-Olivares:typography-overrides
Closed

[web] Listen to text spacing overrides#172915
Renzo-Olivares wants to merge 114 commits into
flutter:masterfrom
Renzo-Olivares:typography-overrides

Conversation

@Renzo-Olivares

@Renzo-Olivares Renzo-Olivares commented Jul 29, 2025

Copy link
Copy Markdown
Contributor

Framework:

  • EditableText/SelectableText, applies lineHeightScaleFactorOverride, wordSpacingOverride, and letterSpacingOverride to it's TextStyle similarly to how we already do for bold platform overrides. Note SelectableText is built on EditableText so it also applies these overrides.
  • Text, applies lineHeightScaleFactorOverride, wordSpacingOverride, and letterSpacingOverride to it's TextStyle similarly to how we already do for bold platform overrides.
  • Exposes line height override through MediaQueryData.lineHeightScaleFactorOverride and maybeLineHeightScaleFactorOverrideOf(context).
  • Exposes letter spacing override through MediaQueryData.letterSpacingOverride and maybeLetterSpacingOverrideOf(context).
  • Exposes word spacing override through MediaQueryData.wordSpacingOverride and maybeWordSpacingOverrideOf(context).
  • Exposes paragraph spacing override through MediaQueryData.paragraphSpacingOverride and maybeParagraphSpacingOf(context).

Engine:

  • Introduces new members on PlatformDispatcher API that hold the text spacing properties that are overridden on the web.
  • We provide the lineHeightScaleFactorOverride, letterSpacingOverride, wordSpacingOverride, and paragraphSpacingOverride on the web by attaching a ResizeObserver to an off-screen hidden element, when its size changes we capture its text spacing CSS properties, and notify the framework through onMetricsChanged.

Fixes #142712

Screen.Recording.2025-07-30.at.2.50.37.PM.mov

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I signed the [CLA].
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • I followed the [breaking change policy] and added [Data Driven Fixes] where supported.
  • All existing and new tests are passing.

@github-actions github-actions Bot added a: text input Entering text in a text field or keyboard related problems framework flutter/packages/flutter repository. See also f: labels. engine flutter/engine related. See also e: labels. platform-web Web applications specifically labels Jul 29, 2025

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for platform typography settings on the web, allowing Flutter apps to respect user-defined text spacing for properties like line height, letter spacing, and word spacing. The changes are well-structured, adding a TypographySettings class and plumbing it through the platform dispatcher and MediaQuery. My main feedback is on the web implementation of the DomMutationObserver, which has some robustness issues in detecting style changes. I've provided a suggestion to make it more reliable.

Comment thread engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart Outdated
@Renzo-Olivares Renzo-Olivares changed the title [Web] Listen to text spacing overrides through TypographySettings [web] Listen to text spacing overrides through TypographySettings Jul 30, 2025
@Renzo-Olivares Renzo-Olivares force-pushed the typography-overrides branch 2 times, most recently from 8e12a12 to 899258a Compare July 31, 2025 20:25
@github-actions github-actions Bot added the f: material design flutter/packages/flutter/material repository. label Aug 4, 2025
@Renzo-Olivares Renzo-Olivares force-pushed the typography-overrides branch 5 times, most recently from 8dce1f5 to 85239f2 Compare August 7, 2025 22:40
@Renzo-Olivares Renzo-Olivares marked this pull request as ready for review August 7, 2025 23:44
return;
}
// Disable text spacing properties.
_updateTypographySettings(const ui.TypographySettings());

@Renzo-Olivares Renzo-Olivares Aug 7, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of setting this to an empty TypographySettings to disable this but I don't see another way since configuration.copyWith(typographySettings:null) won't work. I could build a method that makes a new PlatformConfiguration with TypographySettings cleared but it wouldn't be very maintainable if we keep adding members to PlatformConfiguration.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I vaguely remember somewhere we do something like

void copyWith({SomeClass? object = _somePivateStaticObject}) {
  if (object != _somePivateStaticObject) {
     // do replace
  }
}

is assigning typographySettings an empty TypographySettings or null the same thing? if so we should only pick one of them to avoid this ambiguous. For example just make typographySettings to be not nullable and default to a static/const empty value.

Comment thread engine/src/flutter/lib/ui/window.dart Outdated
});

/// The height of the text, as a multiple of the font size.
final double? lineHeight;

@Renzo-Olivares Renzo-Olivares Aug 7, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be lineHeightFactor instead? It seems like a more appropriate name.

In CSS line-height can accept:

  • Default: normal
  • Normal keyword
  • Unitless, relative to font size, for example 2.
  • <length>, number followed by units 10px
  • <percentage>, number followed by % sign 10%, relative to font size

but there seems to be a strong preference for unitless numbers, i.e. numbers relative to font size.

https://developer.mozilla.org/en-US/docs/Web/CSS/line-height#prefer_unitless_numbers_for_line-height_values

In the extension and bookmarklet I tried both are using unitless values for their CSS.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to support other type of unit? If so, can a double support both unitless and px ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it doesn't make sense to set line height to an absolute number for a11y purposes. Percentage makes sense but it's basically the same as unitless, i.e. 150% == 1.5.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think double can support unitless and px, from what I understand and my observations getComputedStyle will return px values, for word-spacing, letter-spacing, and margin-bottom we can use these 1:1 for their framework counter parts TextStyle.wordSpacing, TextStyle.letterSpacing, and Padding. For line-height we can divide it by the font size and get the lineHeightFactor which would be 1:1 with the frameworks TextStyle.height. So lineHeight would be a factor of fontSize, similar to the frameworks representation.

);
}
if (typographySettings != null && typographySettings.paragraphSpacing != null) {
result = Padding(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious to hear people's thoughts about implementing paragraphSpacing like this. On the web the extension and bookmarklet i've been using implement it by using margin-bottom on the p element.

@chunhtai chunhtai Aug 14, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think gpay has internal code that turns html into TextSpans

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, if they're converting for example <p> elements into TextSpans with the expectation that those <p> elements have paragraphSpacing applied to them, then that will likely not work. They would have to instead turn <p> elements into Text widgets for that expectation to be true.

@chunhtai chunhtai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applies paragraph spacing (margin-bottom) by wrapping its contents with a bottom Padding.

Will this handle cases where developer use \n\n to generate paragraph within a single text widget? or is this something that we don't care about?

Comment thread engine/src/flutter/lib/ui/window.dart Outdated
});

/// The height of the text, as a multiple of the font size.
final double? lineHeight;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to support other type of unit? If so, can a double support both unitless and px ?

return;
}
// Disable text spacing properties.
_updateTypographySettings(const ui.TypographySettings());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I vaguely remember somewhere we do something like

void copyWith({SomeClass? object = _somePivateStaticObject}) {
  if (object != _somePivateStaticObject) {
     // do replace
  }
}

is assigning typographySettings an empty TypographySettings or null the same thing? if so we should only pick one of them to avoid this ambiguous. For example just make typographySettings to be not nullable and default to a static/const empty value.

autofillHints: null,
contextMenuBuilder: widget.contextMenuBuilder,
);
final ui.TypographySettings? typographySettings = MediaQuery.maybeTypographySettingsOf(context);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not something that blocks this pr

I noticed we start to put more and more text related settings into MediaQuery, but we also have DefaultTextStyle. What are the guideline to decide whether something should be in one vs the other?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I feel like this feature is sort of outside the scope of DefaultTextStyle. I think of DefaultTextStyle as a configuration provider that is set up by the application developer, TypographySettings is a configuration set up by the user, and cannot really be updated on the application developers end. So maybe DefaultTextStyle should only include app level configuration, while MediaQuery/TypographySettings includes user level configurations. What do you think about this? I think text scale factor and maybe bolt text would be in line with the type of settings included in TypographySettings.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if one day they start to have overlaps? for example, if one day Android let's user to set the system wide font style. Do we duplicate both in DefaultTextStyle and mediaQuery?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case it feels like it would still be important to distinguish between application level configuration and system wide configuration in the case one is preferred over the other. It also feels like this type of system setting is unlikely to exposed because it is a setting that would affect many other apps as well, so the overlap might not be something we have to consider.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MediaQuery should be considered application level configuration. It is just by default Material app will inherited system level configuration into default MediaQuery. The developer can still override them if they want.

I guess my question is why don't we put TextStyle direclty in MediaQuery to carry the things like text space, or let TextStyle carry a TypographySettings as its property and remove the text spacing, line high...etc.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have enough context to make a suggestion on which paradigm is better, so it is your call. I am just feel a bit confused at how we structure these settings in different part of widget tree. For example. system provides a,b,c,d all meant for Text-related widget.

when it reaches mediaquery, it bundles (a,b) into a class so it carries (a,b), c, d.

but when it reach widget it unwrap and re-bundled b and c, and it becomes a, (b, c), d.

It is a bit hard to wrap my head around these logical data class in each layer

@Renzo-Olivares Renzo-Olivares Sep 12, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the clarification, I think I understand your concerns better. As a user it is strange that different text settings are spread across MediaQuery instead of consolidated in one.

In the current state a user needs to pull three members from MediaQuery and apply them across 2 different classes with majority being in TextStyle:

  • TypographySettings - applies to TextStyle, and Padding widget.
  • boldText - applies to TextStyle.
  • textScaler - applies to TextStyle.

it would make sense if a user only had to pull one member from MediaQuery to access text related settings.

final ui.TypographySettings? typographySettings = MediaQuery.maybeTypographySettingsOf(context);
effectiveTextStyle = effectiveTextStyle!.merge(
  TextStyle(
    height: typographySettings?.lineHeight,//a
    letterSpacing: typographySettings?.letterSpacing,//b
    wordSpacing: typographySettings?.wordSpacing,//c
    fontWeight: MediaQuery.boldTextOf(context) ? FontWeight.bold : null,//d
  ),
);
if (typographySettings != null && typographySettings.paragraphSpacing != null) {
  result = Padding(
    padding: EdgeInsets.only(bottom: typographySettings.paragraphSpacing!),//e
    child: result,
  );
}

@loic-sharma loic-sharma Sep 15, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I'm loving this excellent discussion!)

I think what Chun-Heng is suggesting is putting all TypographySettings on MediaQueryData directly.

Something like:

final double? lineScaleFactor = MediaQuery.lineScaleFactorOf(context);
final double? letterSpacing = MediaQuery.letterSpacingOf(context);
final double? wordSpacing = MediaQuery.wordSpacingOf(context);
final double? paragraphSpacing = MediaQuery.paragraphSpacingOf(context);
final FontWeight? fontWeight = MediaQuery.boldTextOf(context) ? FontWeight.bold : null;

if (lineScaleFactor != null || letterSpacing != null || ... ) {
  effectiveTextStyle = effectiveTextStyle!.merge(
    TextStyle(
      height: lineScaleFactor,//a
      letterSpacing: letterSpacing,//b
      wordSpacing: wordSpacing,//c
      fontWeight: fontWeight,//d
  ),
);
if (paragraphSpacing != null) {
  result = Padding(
    padding: EdgeInsets.only(bottom: paragraphSpacing!),//e
    child: result,
  );
}

This API does seem more consistent to me.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the API suggestions @loic-sharma! I agree putting the settings directly on MediaQueryData looks more clean and consistent.

@chunhtai chunhtai Sep 22, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup that sounds good to me.

Yes I was suggesting two extreme, this or bundle all text related property in on class, anything in between seems weird. Since MediaQuery has been going with putting everything directly in mediaQuery. so I think the safest for now is to go with the current pattern. we can revisit it later if we want to change that and bundle every thing together in the furture

/// supported.
final bool supportsShowingSystemContextMenu;

/// The typography settings for the view this media query is derived from.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe,

The user's preferred typography settings.

Comment thread packages/flutter/lib/src/widgets/editable_text.dart Outdated
);
}
if (typographySettings != null && typographySettings.paragraphSpacing != null) {
result = Padding(

@chunhtai chunhtai Aug 14, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think gpay has internal code that turns html into TextSpans

@Renzo-Olivares

Copy link
Copy Markdown
Contributor Author

applies paragraph spacing (margin-bottom) by wrapping its contents with a bottom Padding.

Will this handle cases where developer use \n\n to generate paragraph within a single text widget? or is this something that we don't care about?

I want to say we don't really care about that case since the web doesn't seem to properly handle that case either. The extensions and bookmarklet's i've tried only apply "paragraphSpacing" between "p" elements.

Comment on lines +85 to +87
if (parsed != null && !parsed.isNaN) {
styleProperty = parsed;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (parsed != null && !parsed.isNaN) {
styleProperty = parsed;
}
styleProperty = parsed;

The extra checks are not necessary. parseFloat() already handles the isNaN case.

Comment thread engine/src/flutter/lib/ui/window.dart Outdated
});

/// The height of the text, as a multiple of the font size.
final double? lineHeight;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it doesn't make sense to set line height to an absolute number for a11y purposes. Percentage makes sense but it's basically the same as unitless, i.e. 150% == 1.5.

Comment on lines +1097 to +1100
style.lineHeight = '${spacingDefault}px';
style.letterSpacing = '${spacingDefault}px';
style.wordSpacing = '${spacingDefault}px';
style.margin = '0px 0px ${spacingDefault}px 0px';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applying these default styles directly on the element, are browser extensions able to override them via global styles?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm understanding correctly yes they are able to override them via global styles, at least from the ones i've tried.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally, inline styles that are set directly on the element (which is what you are doing here) will override any global styles. But extensions are using !important to force their styles to take precedence: https://github.com/actum/text-spacing-editor/blob/6782a09bfd55d5ad7791c498804ecfbf28ece0dc/helpers/buildCSSToInject.ts#L20-L30

So you are good 🙂

Comment thread engine/src/flutter/lib/ui/window.dart Outdated
@chunhtai chunhtai self-requested a review August 29, 2025 16:11
@Renzo-Olivares Renzo-Olivares force-pushed the typography-overrides branch 2 times, most recently from b45142c to 582f2e5 Compare September 2, 2025 23:13
@github-actions github-actions Bot added the a: tests "flutter test", flutter_test, or one of our tests label Sep 3, 2025
@github-actions github-actions Bot added the f: cupertino flutter/packages/flutter/cupertino repository label Nov 5, 2025

@LongCatIsLooong LongCatIsLooong left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with nits.

bool? forceStrutHeight,
String? debugLabel,
String? package,
}) : assert(fontSize == null || fontSize > 0),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still the font size can't be negative? or this is checked in the unnamed constructor?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is checked in the unnamed constructor.

String? package,
}) : assert(fontSize == null || fontSize > 0),
assert(leading == null || leading >= 0),
assert(package == null || fontFamily != null || fontFamilyFallback != null),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these two asserts also in the unnamed constructor?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes these asserts are also present in the unnamed constructor.

}) : this(
fontFamily: fontFamily != null
? (package == null ? fontFamily : 'packages/$package/$fontFamily')
: textStyle.fontFamily,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is not a bug introduced in this patch, but the fontFamilyFallback needs to embed the package path too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it does end up doing this but through a getter.

List<String>? get fontFamilyFallback {
  if (_package != null && _fontFamilyFallback != null) {
    return _fontFamilyFallback.map((String family) => 'packages/$_package/$family').toList();
  }
  return _fontFamilyFallback;
}


spellCheckResults = SpellCheckResults(text, suggestions);
renderEditable.text = buildTextSpan();
final double? lineHeightScaleFactor = mounted

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why's the mounted check needed? Generally the state should unregister itself from these services in dispose?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were no mounted checks before.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this logic is happening in an async method, the lint complained we were not checking mounted.

// must also be updated.
// TODO(Renzo-Olivares): Remove after investigating a solution for overriding all
// styles for children in an [InlineSpan] tree, see: https://github.com/flutter/flutter/issues/177952.
class _OverridingTextStyleTextSpanUtils {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can these just be two private functions without the utility class?

return TextSpan(
text: textSpan.text,
children: textSpan.children?.map((InlineSpan child) {
if (child is TextSpan && child.runtimeType == TextSpan) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the runtimeType == check? IIRC the reason we need the is TextSpan is that InlineSpan doesn't specify its child model. But TextSpan does specify its child model.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone is adding new features to their TextSpan subclass they would be lost during this recreation because we don’t own the subclass and cannot recreate it accurately.

/// supported.
final bool supportsShowingSystemContextMenu;

/// The height of the text, as a multiple of the font size.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: height override? same for the other two parameters since they take precedence over existing local text style values?

@Renzo-Olivares

Copy link
Copy Markdown
Contributor Author

Closing this in favor of #178081

github-merge-queue Bot pushed a commit that referenced this pull request Nov 11, 2025
Original PR/Discussion: #172915

# Framework:
* `EditableText`/`SelectableText`, applies
`lineHeightScaleFactorOverride`, `wordSpacingOverride`, and
`letterSpacingOverride` to it's `TextStyle` similarly to how we already
do for bold platform overrides. Note `SelectableText` is built on
`EditableText` so it also applies these overrides.
* `Text`, applies `lineHeightScaleFactorOverride`,
`wordSpacingOverride`, and `letterSpacingOverride` to it's `TextStyle`
similarly to how we already do for bold platform overrides.
* Exposes line height override through
`MediaQueryData.lineHeightScaleFactorOverride` and
`maybeLineHeightScaleFactorOverrideOf(context)`.
* Exposes letter spacing override through
`MediaQueryData.letterSpacingOverride` and
`maybeLetterSpacingOverrideOf(context)`.
* Exposes word spacing override through
`MediaQueryData.wordSpacingOverride` and
`maybeWordSpacingOverrideOf(context)`.
* Exposes paragraph spacing override through
`MediaQueryData.paragraphSpacingOverride` and
`maybeParagraphSpacingOverrideOf(context)`.
* `MediaQuery.applyTextStyleOverrides()` \
`MediaQueryData.applyTextStyleOverrides()` to be able to reset/override
the text spacing settings on `MediaQueryData`.

# Engine:
* Introduces new members on `PlatformDispatcher` API that hold the text
spacing properties that are overridden on the web.
* We provide the `lineHeightScaleFactorOverride`,
`letterSpacingOverride`, `wordSpacingOverride`, and
`paragraphSpacingOverride` on the web by attaching a `ResizeObserver` to
an off-screen hidden element, when its size changes we capture its text
spacing CSS properties, and notify the framework through
`onMetricsChanged`.

Fixes #142712


https://github.com/user-attachments/assets/aaaa3e74-c232-4956-acd2-ae1a4487e415

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

---------

Co-authored-by: Renzo Olivares <roliv@google.com>
IvoneDjaja pushed a commit to IvoneDjaja/flutter that referenced this pull request Nov 22, 2025
Original PR/Discussion: flutter#172915

# Framework:
* `EditableText`/`SelectableText`, applies
`lineHeightScaleFactorOverride`, `wordSpacingOverride`, and
`letterSpacingOverride` to it's `TextStyle` similarly to how we already
do for bold platform overrides. Note `SelectableText` is built on
`EditableText` so it also applies these overrides.
* `Text`, applies `lineHeightScaleFactorOverride`,
`wordSpacingOverride`, and `letterSpacingOverride` to it's `TextStyle`
similarly to how we already do for bold platform overrides.
* Exposes line height override through
`MediaQueryData.lineHeightScaleFactorOverride` and
`maybeLineHeightScaleFactorOverrideOf(context)`.
* Exposes letter spacing override through
`MediaQueryData.letterSpacingOverride` and
`maybeLetterSpacingOverrideOf(context)`.
* Exposes word spacing override through
`MediaQueryData.wordSpacingOverride` and
`maybeWordSpacingOverrideOf(context)`.
* Exposes paragraph spacing override through
`MediaQueryData.paragraphSpacingOverride` and
`maybeParagraphSpacingOverrideOf(context)`.
* `MediaQuery.applyTextStyleOverrides()` \
`MediaQueryData.applyTextStyleOverrides()` to be able to reset/override
the text spacing settings on `MediaQueryData`.

# Engine:
* Introduces new members on `PlatformDispatcher` API that hold the text
spacing properties that are overridden on the web.
* We provide the `lineHeightScaleFactorOverride`,
`letterSpacingOverride`, `wordSpacingOverride`, and
`paragraphSpacingOverride` on the web by attaching a `ResizeObserver` to
an off-screen hidden element, when its size changes we capture its text
spacing CSS properties, and notify the framework through
`onMetricsChanged`.

Fixes flutter#142712


https://github.com/user-attachments/assets/aaaa3e74-c232-4956-acd2-ae1a4487e415

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

---------

Co-authored-by: Renzo Olivares <roliv@google.com>
mboetger pushed a commit to mboetger/flutter that referenced this pull request Dec 2, 2025
Original PR/Discussion: flutter#172915

# Framework:
* `EditableText`/`SelectableText`, applies
`lineHeightScaleFactorOverride`, `wordSpacingOverride`, and
`letterSpacingOverride` to it's `TextStyle` similarly to how we already
do for bold platform overrides. Note `SelectableText` is built on
`EditableText` so it also applies these overrides.
* `Text`, applies `lineHeightScaleFactorOverride`,
`wordSpacingOverride`, and `letterSpacingOverride` to it's `TextStyle`
similarly to how we already do for bold platform overrides.
* Exposes line height override through
`MediaQueryData.lineHeightScaleFactorOverride` and
`maybeLineHeightScaleFactorOverrideOf(context)`.
* Exposes letter spacing override through
`MediaQueryData.letterSpacingOverride` and
`maybeLetterSpacingOverrideOf(context)`.
* Exposes word spacing override through
`MediaQueryData.wordSpacingOverride` and
`maybeWordSpacingOverrideOf(context)`.
* Exposes paragraph spacing override through
`MediaQueryData.paragraphSpacingOverride` and
`maybeParagraphSpacingOverrideOf(context)`.
* `MediaQuery.applyTextStyleOverrides()` \
`MediaQueryData.applyTextStyleOverrides()` to be able to reset/override
the text spacing settings on `MediaQueryData`.

# Engine:
* Introduces new members on `PlatformDispatcher` API that hold the text
spacing properties that are overridden on the web.
* We provide the `lineHeightScaleFactorOverride`,
`letterSpacingOverride`, `wordSpacingOverride`, and
`paragraphSpacingOverride` on the web by attaching a `ResizeObserver` to
an off-screen hidden element, when its size changes we capture its text
spacing CSS properties, and notify the framework through
`onMetricsChanged`.

Fixes flutter#142712


https://github.com/user-attachments/assets/aaaa3e74-c232-4956-acd2-ae1a4487e415

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

---------

Co-authored-by: Renzo Olivares <roliv@google.com>
reidbaker pushed a commit to AbdeMohlbi/flutter that referenced this pull request Dec 10, 2025
Original PR/Discussion: flutter#172915

# Framework:
* `EditableText`/`SelectableText`, applies
`lineHeightScaleFactorOverride`, `wordSpacingOverride`, and
`letterSpacingOverride` to it's `TextStyle` similarly to how we already
do for bold platform overrides. Note `SelectableText` is built on
`EditableText` so it also applies these overrides.
* `Text`, applies `lineHeightScaleFactorOverride`,
`wordSpacingOverride`, and `letterSpacingOverride` to it's `TextStyle`
similarly to how we already do for bold platform overrides.
* Exposes line height override through
`MediaQueryData.lineHeightScaleFactorOverride` and
`maybeLineHeightScaleFactorOverrideOf(context)`.
* Exposes letter spacing override through
`MediaQueryData.letterSpacingOverride` and
`maybeLetterSpacingOverrideOf(context)`.
* Exposes word spacing override through
`MediaQueryData.wordSpacingOverride` and
`maybeWordSpacingOverrideOf(context)`.
* Exposes paragraph spacing override through
`MediaQueryData.paragraphSpacingOverride` and
`maybeParagraphSpacingOverrideOf(context)`.
* `MediaQuery.applyTextStyleOverrides()` \
`MediaQueryData.applyTextStyleOverrides()` to be able to reset/override
the text spacing settings on `MediaQueryData`.

# Engine:
* Introduces new members on `PlatformDispatcher` API that hold the text
spacing properties that are overridden on the web.
* We provide the `lineHeightScaleFactorOverride`,
`letterSpacingOverride`, `wordSpacingOverride`, and
`paragraphSpacingOverride` on the web by attaching a `ResizeObserver` to
an off-screen hidden element, when its size changes we capture its text
spacing CSS properties, and notify the framework through
`onMetricsChanged`.

Fixes flutter#142712


https://github.com/user-attachments/assets/aaaa3e74-c232-4956-acd2-ae1a4487e415

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

---------

Co-authored-by: Renzo Olivares <roliv@google.com>
chunhtai pushed a commit to chunhtai/packages that referenced this pull request Jun 12, 2026
Original PR/Discussion: flutter/flutter#172915

# Framework:
* `EditableText`/`SelectableText`, applies
`lineHeightScaleFactorOverride`, `wordSpacingOverride`, and
`letterSpacingOverride` to it's `TextStyle` similarly to how we already
do for bold platform overrides. Note `SelectableText` is built on
`EditableText` so it also applies these overrides.
* `Text`, applies `lineHeightScaleFactorOverride`,
`wordSpacingOverride`, and `letterSpacingOverride` to it's `TextStyle`
similarly to how we already do for bold platform overrides.
* Exposes line height override through
`MediaQueryData.lineHeightScaleFactorOverride` and
`maybeLineHeightScaleFactorOverrideOf(context)`.
* Exposes letter spacing override through
`MediaQueryData.letterSpacingOverride` and
`maybeLetterSpacingOverrideOf(context)`.
* Exposes word spacing override through
`MediaQueryData.wordSpacingOverride` and
`maybeWordSpacingOverrideOf(context)`.
* Exposes paragraph spacing override through
`MediaQueryData.paragraphSpacingOverride` and
`maybeParagraphSpacingOverrideOf(context)`.
* `MediaQuery.applyTextStyleOverrides()` \
`MediaQueryData.applyTextStyleOverrides()` to be able to reset/override
the text spacing settings on `MediaQueryData`.

# Engine:
* Introduces new members on `PlatformDispatcher` API that hold the text
spacing properties that are overridden on the web.
* We provide the `lineHeightScaleFactorOverride`,
`letterSpacingOverride`, `wordSpacingOverride`, and
`paragraphSpacingOverride` on the web by attaching a `ResizeObserver` to
an off-screen hidden element, when its size changes we capture its text
spacing CSS properties, and notify the framework through
`onMetricsChanged`.

Fixes #142712


https://github.com/user-attachments/assets/aaaa3e74-c232-4956-acd2-ae1a4487e415

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

---------

Co-authored-by: Renzo Olivares <roliv@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: tests "flutter test", flutter_test, or one of our tests a: text input Entering text in a text field or keyboard related problems engine flutter/engine related. See also e: labels. f: cupertino flutter/packages/flutter/cupertino repository framework flutter/packages/flutter repository. See also f: labels. platform-web Web applications specifically

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[web:a11y] respect text spacing browser settings

5 participants