From 61262dd03df330b1f9ddde659b7bace5c57ad785 Mon Sep 17 00:00:00 2001 From: Aleksandr Lozhkovoi Date: Tue, 22 Sep 2026 19:55:44 +0200 Subject: [PATCH 1/4] [material_ui] Prefer disabledTextColor for disabled MaterialButton (#127449) --- .../material_ui/lib/src/button_theme.dart | 2 +- .../change_2026_09_22_1790099179515.yaml | 3 +++ .../test/material_button_test.dart | 26 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 packages/material_ui/pending_changelogs/change_2026_09_22_1790099179515.yaml diff --git a/packages/material_ui/lib/src/button_theme.dart b/packages/material_ui/lib/src/button_theme.dart index f59f15c59b20..cdb9d5004aa4 100644 --- a/packages/material_ui/lib/src/button_theme.dart +++ b/packages/material_ui/lib/src/button_theme.dart @@ -396,7 +396,7 @@ class ButtonThemeData with Diagnosticable { /// If [MaterialButton.textColor] is a [WidgetStateProperty], it will be /// used as the `disabledTextColor`. It will be resolved in the [WidgetState.disabled] state. Color getDisabledTextColor(MaterialButton button) { - return button.textColor ?? button.disabledTextColor ?? colorScheme!.onSurface.withOpacity(0.38); + return button.disabledTextColor ?? button.textColor ?? colorScheme!.onSurface.withOpacity(0.38); } /// The [button]'s background color when [MaterialButton.onPressed] is null diff --git a/packages/material_ui/pending_changelogs/change_2026_09_22_1790099179515.yaml b/packages/material_ui/pending_changelogs/change_2026_09_22_1790099179515.yaml new file mode 100644 index 000000000000..a017cc0b4b7f --- /dev/null +++ b/packages/material_ui/pending_changelogs/change_2026_09_22_1790099179515.yaml @@ -0,0 +1,3 @@ +changelog: | + - Fixes MaterialButton using textColor instead of disabledTextColor when disabled. +version: patch diff --git a/packages/material_ui/test/material_button_test.dart b/packages/material_ui/test/material_button_test.dart index 8ed9b0f8c3c9..6eac03df964b 100644 --- a/packages/material_ui/test/material_button_test.dart +++ b/packages/material_ui/test/material_button_test.dart @@ -275,6 +275,32 @@ void main() { expect(material.color, const Color(0xff00ff00)); }); + testWidgets('Disabled MaterialButton prefers disabledTextColor over textColor', ( + WidgetTester tester, + ) async { + // Regression test for https://github.com/flutter/flutter/issues/127449. + + final Finder rawButtonMaterial = find.descendant( + of: find.byType(MaterialButton), + matching: find.byType(Material), + ); + + await tester.pumpWidget( + const Directionality( + textDirection: TextDirection.ltr, + child: MaterialButton( + onPressed: null, + textColor: Colors.blue, + disabledTextColor: Colors.red, + child: Text('button'), + ), + ), + ); + + final Material material = tester.widget(rawButtonMaterial); + expect(material.textStyle!.color, Colors.red); + }); + testWidgets( 'Default MaterialButton meets a11y contrast guidelines', (WidgetTester tester) async { From 0e5e0c15fb1a59b5a29c3ff2749d883e3dff4df2 Mon Sep 17 00:00:00 2001 From: Aleksandr Lozhkovoi Date: Tue, 22 Sep 2026 20:03:48 +0200 Subject: [PATCH 2/4] [material_ui] Preserve stateful text color precedence --- .../material_ui/lib/src/button_theme.dart | 3 +++ .../test/material_button_test.dart | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/packages/material_ui/lib/src/button_theme.dart b/packages/material_ui/lib/src/button_theme.dart index cdb9d5004aa4..a466885edf0d 100644 --- a/packages/material_ui/lib/src/button_theme.dart +++ b/packages/material_ui/lib/src/button_theme.dart @@ -396,6 +396,9 @@ class ButtonThemeData with Diagnosticable { /// If [MaterialButton.textColor] is a [WidgetStateProperty], it will be /// used as the `disabledTextColor`. It will be resolved in the [WidgetState.disabled] state. Color getDisabledTextColor(MaterialButton button) { + if (button.textColor is WidgetStateProperty) { + return button.textColor!; + } return button.disabledTextColor ?? button.textColor ?? colorScheme!.onSurface.withOpacity(0.38); } diff --git a/packages/material_ui/test/material_button_test.dart b/packages/material_ui/test/material_button_test.dart index 6eac03df964b..17acbcb7e307 100644 --- a/packages/material_ui/test/material_button_test.dart +++ b/packages/material_ui/test/material_button_test.dart @@ -301,6 +301,32 @@ void main() { expect(material.textStyle!.color, Colors.red); }); + for (final Color? disabledTextColor in [null, Colors.red]) { + testWidgets( + 'Disabled MaterialButton resolves stateful textColor with disabledTextColor=$disabledTextColor', + (WidgetTester tester) async { + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: MaterialButton( + onPressed: null, + textColor: WidgetStateColor.resolveWith((Set states) { + return states.contains(WidgetState.disabled) ? Colors.grey : Colors.blue; + }), + disabledTextColor: disabledTextColor, + child: const Text('button'), + ), + ), + ); + + final Material material = tester.widget( + find.descendant(of: find.byType(MaterialButton), matching: find.byType(Material)), + ); + expect(material.textStyle!.color, Colors.grey); + }, + ); + } + testWidgets( 'Default MaterialButton meets a11y contrast guidelines', (WidgetTester tester) async { From f8b6f750473e246fe07226b9d051287075682626 Mon Sep 17 00:00:00 2001 From: Aleksandr Lozhkovoi Date: Tue, 22 Sep 2026 20:04:12 +0200 Subject: [PATCH 3/4] [material_ui] Follow inferred variable type style in regression test --- packages/material_ui/test/material_button_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/material_ui/test/material_button_test.dart b/packages/material_ui/test/material_button_test.dart index 17acbcb7e307..3d54b588af1a 100644 --- a/packages/material_ui/test/material_button_test.dart +++ b/packages/material_ui/test/material_button_test.dart @@ -301,7 +301,7 @@ void main() { expect(material.textStyle!.color, Colors.red); }); - for (final Color? disabledTextColor in [null, Colors.red]) { + for (final disabledTextColor in [null, Colors.red]) { testWidgets( 'Disabled MaterialButton resolves stateful textColor with disabledTextColor=$disabledTextColor', (WidgetTester tester) async { From f536a93d6c79352769d2e377c0c8488c695b016b Mon Sep 17 00:00:00 2001 From: Aleksandr Lozhkovoi Date: Tue, 22 Sep 2026 20:07:12 +0200 Subject: [PATCH 4/4] [material_ui] Split stateful color coverage into explicit tests --- .../test/material_button_test.dart | 64 ++++++++++++------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/packages/material_ui/test/material_button_test.dart b/packages/material_ui/test/material_button_test.dart index 3d54b588af1a..b56dc9863992 100644 --- a/packages/material_ui/test/material_button_test.dart +++ b/packages/material_ui/test/material_button_test.dart @@ -301,31 +301,51 @@ void main() { expect(material.textStyle!.color, Colors.red); }); - for (final disabledTextColor in [null, Colors.red]) { - testWidgets( - 'Disabled MaterialButton resolves stateful textColor with disabledTextColor=$disabledTextColor', - (WidgetTester tester) async { - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: MaterialButton( - onPressed: null, - textColor: WidgetStateColor.resolveWith((Set states) { - return states.contains(WidgetState.disabled) ? Colors.grey : Colors.blue; - }), - disabledTextColor: disabledTextColor, - child: const Text('button'), - ), + testWidgets( + 'Disabled MaterialButton resolves stateful textColor when disabledTextColor is null', + (WidgetTester tester) async { + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: MaterialButton( + onPressed: null, + textColor: WidgetStateColor.resolveWith((Set states) { + return states.contains(WidgetState.disabled) ? Colors.grey : Colors.blue; + }), + child: const Text('button'), ), - ); + ), + ); - final Material material = tester.widget( - find.descendant(of: find.byType(MaterialButton), matching: find.byType(Material)), - ); - expect(material.textStyle!.color, Colors.grey); - }, + final Material material = tester.widget( + find.descendant(of: find.byType(MaterialButton), matching: find.byType(Material)), + ); + expect(material.textStyle!.color, Colors.grey); + }, + ); + + testWidgets('Disabled MaterialButton prefers stateful textColor over disabledTextColor', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: MaterialButton( + onPressed: null, + textColor: WidgetStateColor.resolveWith((Set states) { + return states.contains(WidgetState.disabled) ? Colors.grey : Colors.blue; + }), + disabledTextColor: Colors.red, + child: const Text('button'), + ), + ), + ); + + final Material material = tester.widget( + find.descendant(of: find.byType(MaterialButton), matching: find.byType(Material)), ); - } + expect(material.textStyle!.color, Colors.grey); + }); testWidgets( 'Default MaterialButton meets a11y contrast guidelines',