diff --git a/packages/material_ui/lib/src/_about_io.dart b/packages/material_ui/lib/src/_about_io.dart
new file mode 100644
index 00000000000..bda9d23d9f0
--- /dev/null
+++ b/packages/material_ui/lib/src/_about_io.dart
@@ -0,0 +1,11 @@
+// Copyright 2013 The Flutter Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:io' show Platform;
+
+/// The file name of the currently running executable.
+///
+/// Used as the fallback application name by the about dialog widgets when
+/// no `Title` ancestor is available.
+String get executableName => Platform.resolvedExecutable.split(Platform.pathSeparator).last;
diff --git a/packages/material_ui/lib/src/_about_web.dart b/packages/material_ui/lib/src/_about_web.dart
new file mode 100644
index 00000000000..6da44e22387
--- /dev/null
+++ b/packages/material_ui/lib/src/_about_web.dart
@@ -0,0 +1,8 @@
+// Copyright 2013 The Flutter Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// The file name of the currently running executable.
+///
+/// There is no executable on the web, so this is always the empty string.
+String get executableName => '';
diff --git a/packages/material_ui/lib/src/about.dart b/packages/material_ui/lib/src/about.dart
index be76f7f4ccb..28d2df8a4d3 100644
--- a/packages/material_ui/lib/src/about.dart
+++ b/packages/material_ui/lib/src/about.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+/// @docImport 'dart:io';
+///
/// @docImport 'package:cupertino_ui/cupertino_ui.dart';
///
/// @docImport 'drawer.dart';
@@ -9,13 +11,13 @@
library;
import 'dart:developer' show Flow, Timeline;
-import 'dart:io' show Platform;
import 'package:cupertino_ui/cupertino_ui.dart' show CupertinoDialogAction;
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart' hide Flow;
+import '_about_io.dart' if (dart.library.js_interop) '_about_web.dart' as about;
import 'app_bar.dart';
import 'back_button.dart';
import 'card.dart';
@@ -1201,7 +1203,7 @@ String _defaultApplicationName(BuildContext context) {
// can provide an explicit applicationName to the widgets defined in this
// file, instead of relying on the default.
final Title? ancestorTitle = context.findAncestorWidgetOfExactType
();
- return ancestorTitle?.title ?? Platform.resolvedExecutable.split(Platform.pathSeparator).last;
+ return ancestorTitle?.title ?? about.executableName;
}
String _defaultApplicationVersion(BuildContext context) {
diff --git a/packages/material_ui/pending_changelogs/change_2026_09_07_about_dart_io.yaml b/packages/material_ui/pending_changelogs/change_2026_09_07_about_dart_io.yaml
new file mode 100644
index 00000000000..86ac579ed0c
--- /dev/null
+++ b/packages/material_ui/pending_changelogs/change_2026_09_07_about_dart_io.yaml
@@ -0,0 +1,3 @@
+changelog: |
+ - Removes the unconditional `dart:io` import from `AboutDialog`, so that `material_ui` is correctly detected as supporting the web platform and `showAboutDialog` no longer throws on the web when no application name is available.
+version: patch
diff --git a/packages/material_ui/test/about_test.dart b/packages/material_ui/test/about_test.dart
index c300f76978f..0390e7b8e1e 100644
--- a/packages/material_ui/test/about_test.dart
+++ b/packages/material_ui/test/about_test.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:io' show Platform;
import 'dart:ui';
import 'package:cupertino_ui/cupertino_ui.dart';
@@ -16,6 +17,47 @@ void main() {
LicenseRegistry.reset();
});
+ Widget buildWithoutTitle(Widget child) {
+ return MediaQuery(
+ data: const MediaQueryData(),
+ child: Localizations(
+ locale: const Locale('en', 'US'),
+ delegates: const >[
+ DefaultMaterialLocalizations.delegate,
+ DefaultWidgetsLocalizations.delegate,
+ ],
+ child: Directionality(
+ textDirection: TextDirection.ltr,
+ child: Theme(data: ThemeData(), child: child),
+ ),
+ ),
+ );
+ }
+
+ testWidgets(
+ 'AboutDialog defaults the application name to the executable name without a Title ancestor',
+ (WidgetTester tester) async {
+ // Regression test for https://github.com/flutter/flutter/issues/191887.
+ await tester.pumpWidget(buildWithoutTitle(const AboutDialog()));
+
+ final String expectedName = Platform.resolvedExecutable.split(Platform.pathSeparator).last;
+ expect(find.text(expectedName), findsOneWidget);
+ },
+ skip: kIsWeb, // [intended] There is no executable name on the web.
+ );
+
+ testWidgets(
+ 'AboutDialog does not throw on the web without a Title ancestor',
+ (WidgetTester tester) async {
+ // Regression test for https://github.com/flutter/flutter/issues/191887.
+ await tester.pumpWidget(buildWithoutTitle(const AboutDialog()));
+
+ expect(tester.takeException(), isNull);
+ expect(find.byType(AboutDialog), findsOneWidget);
+ },
+ skip: !kIsWeb, // [intended] Exercises the web-specific fallback.
+ );
+
testWidgets('Material3 has sentence case labels', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(