Add BottomSheetTheme to enable theming color, elevation, shape of BottomSheet (#31318)

* Introduce BottomSheetTheme and shape support for bottom sheet

* Add bottom sheet theme to ThemeData. Use theme in bottom sheet build

* Expose color, elevation, shape to showModalBottomSheet helper

* Expose color, elevation, shape to showBottomSheet helper

* Address PR feedback

* Address PR feedback

* Address additional PR feedback
diff --git a/packages/flutter/lib/material.dart b/packages/flutter/lib/material.dart
index 0d710a2..97d4431 100644
--- a/packages/flutter/lib/material.dart
+++ b/packages/flutter/lib/material.dart
@@ -27,6 +27,7 @@
 export 'src/material/bottom_app_bar_theme.dart';
 export 'src/material/bottom_navigation_bar.dart';
 export 'src/material/bottom_sheet.dart';
+export 'src/material/bottom_sheet_theme.dart';
 export 'src/material/button.dart';
 export 'src/material/button_bar.dart';
 export 'src/material/button_theme.dart';
diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart
index 496c908..63b7d11 100644
--- a/packages/flutter/lib/src/material/bottom_sheet.dart
+++ b/packages/flutter/lib/src/material/bottom_sheet.dart
@@ -8,6 +8,7 @@
 import 'package:flutter/scheduler.dart';
 import 'package:flutter/widgets.dart';
 
+import 'bottom_sheet_theme.dart';
 import 'colors.dart';
 import 'debug.dart';
 import 'material.dart';
@@ -56,14 +57,15 @@
     Key key,
     this.animationController,
     this.enableDrag = true,
-    this.elevation = 0.0,
     this.backgroundColor,
+    this.elevation,
+    this.shape,
     @required this.onClosing,
     @required this.builder,
   }) : assert(enableDrag != null),
        assert(onClosing != null),
        assert(builder != null),
-       assert(elevation != null && elevation >= 0.0),
+       assert(elevation == null || elevation >= 0.0),
        super(key: key);
 
   /// The animation controller that controls the bottom sheet's entrance and
@@ -92,6 +94,13 @@
   /// Default is true.
   final bool enableDrag;
 
+  /// The bottom sheet's background color.
+  ///
+  /// Defines the bottom sheet's [Material.color].
+  ///
+  /// Defaults to null and falls back to [Material]'s default.
+  final Color backgroundColor;
+
   /// The z-coordinate at which to place this material relative to its parent.
   ///
   /// This controls the size of the shadow below the material.
@@ -99,10 +108,12 @@
   /// Defaults to 0. The value is non-negative.
   final double elevation;
 
-  /// The color for the [Material] of the bottom sheet.
+  /// The shape of the bottom sheet.
   ///
-  /// Defaults to [Colors.white]. The value must not be null.
-  final Color backgroundColor;
+  /// Defines the bottom sheet's [Material.shape].
+  ///
+  /// Defaults to null and falls back to [Material]'s default.
+  final ShapeBorder shape;
 
   @override
   _BottomSheetState createState() => _BottomSheetState();
@@ -170,10 +181,16 @@
 
   @override
   Widget build(BuildContext context) {
+    final BottomSheetThemeData bottomSheetTheme = Theme.of(context).bottomSheetTheme;
+    final Color color = widget.backgroundColor ?? bottomSheetTheme.backgroundColor;
+    final double elevation = widget.elevation ?? bottomSheetTheme.elevation ?? 0;
+    final ShapeBorder shape = widget.shape ?? bottomSheetTheme.shape;
+
     final Widget bottomSheet = Material(
       key: _childKey,
-      color: widget.backgroundColor,
-      elevation: widget.elevation,
+      color: color,
+      elevation: elevation,
+      shape: shape,
       child: NotificationListener<DraggableScrollableNotification>(
         onNotification: extentChanged,
         child: widget.builder(context),
@@ -227,12 +244,18 @@
   const _ModalBottomSheet({
     Key key,
     this.route,
+    this.backgroundColor,
+    this.elevation,
+    this.shape,
     this.isScrollControlled = false,
   }) : assert(isScrollControlled != null),
        super(key: key);
 
   final _ModalBottomSheetRoute<T> route;
   final bool isScrollControlled;
+  final Color backgroundColor;
+  final double elevation;
+  final ShapeBorder shape;
 
   @override
   _ModalBottomSheetState<T> createState() => _ModalBottomSheetState<T>();
@@ -279,7 +302,6 @@
               child: CustomSingleChildLayout(
                 delegate: _ModalBottomSheetLayout(animationValue, widget.isScrollControlled),
                 child: BottomSheet(
-                  backgroundColor: widget.route.backgroundColor,
                   animationController: widget.route._animationController,
                   onClosing: () {
                     if (widget.route.isCurrent) {
@@ -287,6 +309,9 @@
                     }
                   },
                   builder: widget.route.builder,
+                  backgroundColor: widget.backgroundColor,
+                  elevation: widget.elevation,
+                  shape: widget.shape,
                 ),
               ),
             ),
@@ -302,8 +327,10 @@
     this.builder,
     this.theme,
     this.barrierLabel,
-    @required this.isScrollControlled,
     this.backgroundColor,
+    this.elevation,
+    this.shape,
+    @required this.isScrollControlled,
     RouteSettings settings,
   }) : assert(isScrollControlled != null),
        super(settings: settings);
@@ -312,6 +339,8 @@
   final ThemeData theme;
   final bool isScrollControlled;
   final Color backgroundColor;
+  final double elevation;
+  final ShapeBorder shape;
 
   @override
   Duration get transitionDuration => _bottomSheetDuration;
@@ -327,6 +356,7 @@
 
   AnimationController _animationController;
 
+
   @override
   AnimationController createAnimationController() {
     assert(_animationController == null);
@@ -341,7 +371,13 @@
     Widget bottomSheet = MediaQuery.removePadding(
       context: context,
       removeTop: true,
-      child: _ModalBottomSheet<T>(route: this, isScrollControlled: isScrollControlled),
+      child: _ModalBottomSheet<T>(
+        route: this,
+        backgroundColor: backgroundColor,
+        elevation: elevation,
+        shape: shape,
+        isScrollControlled: isScrollControlled
+      ),
     );
     if (theme != null)
       bottomSheet = Theme(data: theme, child: bottomSheet);
@@ -384,8 +420,10 @@
 Future<T> showModalBottomSheet<T>({
   @required BuildContext context,
   @required WidgetBuilder builder,
-  bool isScrollControlled = false,
   Color backgroundColor,
+  double elevation,
+  ShapeBorder shape,
+  bool isScrollControlled = false,
 }) {
   assert(context != null);
   assert(builder != null);
@@ -397,8 +435,10 @@
     builder: builder,
     theme: Theme.of(context, shadowThemeOnly: true),
     isScrollControlled: isScrollControlled,
-    backgroundColor: backgroundColor,
     barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
+    backgroundColor: backgroundColor,
+    elevation: elevation,
+    shape: shape,
   ));
 }
 
@@ -440,6 +480,8 @@
   @required BuildContext context,
   @required WidgetBuilder builder,
   Color backgroundColor,
+  double elevation,
+  ShapeBorder shape,
 }) {
   assert(context != null);
   assert(builder != null);
@@ -448,5 +490,7 @@
   return Scaffold.of(context).showBottomSheet<T>(
     builder,
     backgroundColor: backgroundColor,
+    elevation: elevation,
+    shape: shape,
   );
 }
diff --git a/packages/flutter/lib/src/material/bottom_sheet_theme.dart b/packages/flutter/lib/src/material/bottom_sheet_theme.dart
new file mode 100644
index 0000000..0ba47ea
--- /dev/null
+++ b/packages/flutter/lib/src/material/bottom_sheet_theme.dart
@@ -0,0 +1,111 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:ui' show lerpDouble;
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/rendering.dart';
+
+/// Defines default property values for [BottomSheet]'s [Material].
+///
+/// Descendant widgets obtain the current [BottomSheetThemeData] object
+/// using `Theme.of(context).bottomSheetTheme`. Instances of
+/// [BottomSheetThemeData] can be customized with
+/// [BottomSheetThemeData.copyWith].
+///
+/// Typically a [BottomSheetThemeData] is specified as part of the
+/// overall [Theme] with [ThemeData.bottomSheetTheme].
+///
+/// All [BottomSheetThemeData] properties are `null` by default.
+/// When null, the [BottomSheet] will provide its own defaults.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+class BottomSheetThemeData extends Diagnosticable {
+  /// Creates a theme that can be used for [ThemeData.bottomSheetTheme].
+  const BottomSheetThemeData({
+    this.backgroundColor,
+    this.elevation,
+    this.shape,
+  });
+
+  /// Default value for [BottomSheet.backgroundColor].
+  ///
+  /// If null, [BottomSheet] defaults to [Material]'s default.
+  final Color backgroundColor;
+
+  /// Default value for [BottomSheet.elevation].
+  ///
+  /// {@macro flutter.material.material.elevation}
+  ///
+  /// If null, [BottomSheet] defaults to 0.0.
+  final double elevation;
+
+  /// Default value for [BottomSheet.shape].
+  ///
+  /// If null, no overriding shape is specified for [BottomSheet], so the
+  /// [BottomSheet] is rectangular.
+  final ShapeBorder shape;
+
+  /// Creates a copy of this object with the given fields replaced with the
+  /// new values.
+  BottomSheetThemeData copyWith({
+    Color backgroundColor,
+    double elevation,
+    ShapeBorder shape,
+  }) {
+    return BottomSheetThemeData(
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      elevation: elevation ?? this.elevation,
+      shape: shape ?? this.shape,
+    );
+  }
+
+  /// Linearly interpolate between two bottom sheet themes.
+  ///
+  /// If both arguments are null then null is returned.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static BottomSheetThemeData lerp(BottomSheetThemeData a, BottomSheetThemeData b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    return BottomSheetThemeData(
+      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
+      elevation: lerpDouble(a?.elevation, b?.elevation, t),
+      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      backgroundColor,
+      elevation,
+      shape,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    final BottomSheetThemeData typedOther = other;
+    return typedOther.backgroundColor == backgroundColor
+        && typedOther.elevation == elevation
+        && typedOther.shape == shape;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Color>('backgroundColor', backgroundColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
+  }
+}
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index 0066cb9..55e1f41 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -1541,6 +1541,8 @@
     bool isPersistent, {
     AnimationController animationController,
     Color backgroundColor,
+    double elevation,
+    ShapeBorder shape,
   }) {
     assert(() {
       if (widget.bottomSheet != null && isPersistent && _currentBottomSheet != null) {
@@ -1619,6 +1621,8 @@
       builder: builder,
       isPersistent: isPersistent,
       backgroundColor: backgroundColor,
+      elevation: elevation,
+      shape: shape,
     );
 
     if (!isPersistent)
@@ -1673,6 +1677,8 @@
   PersistentBottomSheetController<T> showBottomSheet<T>(
     WidgetBuilder builder, {
     Color backgroundColor,
+    double elevation,
+    ShapeBorder shape,
   }) {
     assert(() {
       if (widget.bottomSheet != null) {
@@ -1694,6 +1700,8 @@
         false,
         animationController: controller,
         backgroundColor: backgroundColor,
+        elevation: elevation,
+        shape: shape,
       );
     });
     return _currentBottomSheet;
@@ -2216,6 +2224,8 @@
     this.builder,
     this.isPersistent = false,
     this.backgroundColor,
+    this.elevation,
+    this.shape,
   }) : super(key: key);
 
   final AnimationController animationController; // we control it, but it must be disposed by whoever created it.
@@ -2225,6 +2235,8 @@
   final WidgetBuilder builder;
   final bool isPersistent;
   final Color backgroundColor;
+  final double elevation;
+  final ShapeBorder shape;
 
   @override
   _StandardBottomSheetState createState() => _StandardBottomSheetState();
@@ -2311,6 +2323,8 @@
             onClosing: widget.onClosing,
             builder: widget.builder,
             backgroundColor: widget.backgroundColor,
+            elevation: widget.elevation,
+            shape: widget.shape,
           ),
         ),
       );
diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart
index b1d7db2..b766d34 100644
--- a/packages/flutter/lib/src/material/theme_data.dart
+++ b/packages/flutter/lib/src/material/theme_data.dart
@@ -11,6 +11,7 @@
 
 import 'app_bar_theme.dart';
 import 'bottom_app_bar_theme.dart';
+import 'bottom_sheet_theme.dart';
 import 'button_theme.dart';
 import 'card_theme.dart';
 import 'chip_theme.dart';
@@ -165,6 +166,7 @@
     Typography typography,
     CupertinoThemeData cupertinoOverrideTheme,
     SnackBarThemeData snackBarTheme,
+    BottomSheetThemeData bottomSheetTheme,
   }) {
     brightness ??= Brightness.light;
     final bool isDark = brightness == Brightness.dark;
@@ -259,6 +261,7 @@
     floatingActionButtonTheme ??= const FloatingActionButtonThemeData();
     cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
     snackBarTheme ??= const SnackBarThemeData();
+    bottomSheetTheme ??= const BottomSheetThemeData();
 
     return ThemeData.raw(
       brightness: brightness,
@@ -313,6 +316,7 @@
       typography: typography,
       cupertinoOverrideTheme: cupertinoOverrideTheme,
       snackBarTheme: snackBarTheme,
+      bottomSheetTheme: bottomSheetTheme,
     );
   }
 
@@ -379,6 +383,7 @@
     @required this.typography,
     @required this.cupertinoOverrideTheme,
     @required this.snackBarTheme,
+    @required this.bottomSheetTheme,
   }) : assert(brightness != null),
        assert(primaryColor != null),
        assert(primaryColorBrightness != null),
@@ -428,7 +433,8 @@
        assert(dialogTheme != null),
        assert(floatingActionButtonTheme != null),
        assert(typography != null),
-       assert(snackBarTheme != null);
+       assert(snackBarTheme != null),
+       assert(bottomSheetTheme != null);
 
   // Warning: make sure these properties are in the exact same order as in
   // hashValues() and in the raw constructor and in the order of fields in
@@ -699,6 +705,9 @@
   /// can be overridden using attributes of this [cupertinoOverrideTheme].
   final CupertinoThemeData cupertinoOverrideTheme;
 
+  /// A theme for customizing the color, elevation, and shape of a bottom sheet.
+  final BottomSheetThemeData bottomSheetTheme;
+
   /// Creates a copy of this theme but with the given fields replaced with the new values.
   ThemeData copyWith({
     Brightness brightness,
@@ -753,6 +762,7 @@
     Typography typography,
     CupertinoThemeData cupertinoOverrideTheme,
     SnackBarThemeData snackBarTheme,
+    BottomSheetThemeData bottomSheetTheme,
   }) {
     cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
     return ThemeData.raw(
@@ -808,6 +818,7 @@
       typography: typography ?? this.typography,
       cupertinoOverrideTheme: cupertinoOverrideTheme ?? this.cupertinoOverrideTheme,
       snackBarTheme: snackBarTheme ?? this.snackBarTheme,
+      bottomSheetTheme: bottomSheetTheme ?? this.bottomSheetTheme,
     );
   }
 
@@ -941,6 +952,7 @@
       typography: Typography.lerp(a.typography, b.typography, t),
       cupertinoOverrideTheme: t < 0.5 ? a.cupertinoOverrideTheme : b.cupertinoOverrideTheme,
       snackBarTheme: SnackBarThemeData.lerp(a.snackBarTheme, b.snackBarTheme, t),
+      bottomSheetTheme: BottomSheetThemeData.lerp(a.bottomSheetTheme, b.bottomSheetTheme, t),
     );
   }
 
@@ -1003,7 +1015,8 @@
            (otherData.floatingActionButtonTheme == floatingActionButtonTheme) &&
            (otherData.typography == typography) &&
            (otherData.cupertinoOverrideTheme == cupertinoOverrideTheme) &&
-           (otherData.snackBarTheme == snackBarTheme);
+           (otherData.snackBarTheme == snackBarTheme) &&
+           (otherData.bottomSheetTheme == bottomSheetTheme);
   }
 
   @override
@@ -1067,6 +1080,7 @@
           typography,
           cupertinoOverrideTheme,
           snackBarTheme,
+          bottomSheetTheme,
         ),
       ),
     );
@@ -1125,6 +1139,7 @@
     properties.add(DiagnosticsProperty<Typography>('typography', typography, defaultValue: defaultData.typography));
     properties.add(DiagnosticsProperty<CupertinoThemeData>('cupertinoOverrideTheme', cupertinoOverrideTheme, defaultValue: defaultData.cupertinoOverrideTheme));
     properties.add(DiagnosticsProperty<SnackBarThemeData>('snackBarTheme', snackBarTheme, defaultValue: defaultData.snackBarTheme));
+    properties.add(DiagnosticsProperty<BottomSheetThemeData>('bottomSheetTheme', bottomSheetTheme, defaultValue: defaultData.bottomSheetTheme));
   }
 }
 
diff --git a/packages/flutter/test/material/bottom_sheet_theme_test.dart b/packages/flutter/test/material/bottom_sheet_theme_test.dart
new file mode 100644
index 0000000..0d19a67
--- /dev/null
+++ b/packages/flutter/test/material/bottom_sheet_theme_test.dart
@@ -0,0 +1,143 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  test('BottomSheetThemeData copyWith, ==, hashCode basics', () {
+    expect(const BottomSheetThemeData(), const BottomSheetThemeData().copyWith());
+    expect(const BottomSheetThemeData().hashCode, const BottomSheetThemeData().copyWith().hashCode);
+  });
+
+  test('BottomSheetThemeData null fields by default', () {
+    const BottomSheetThemeData bottomSheetTheme = BottomSheetThemeData();
+    expect(bottomSheetTheme.backgroundColor, null);
+    expect(bottomSheetTheme.elevation, null);
+    expect(bottomSheetTheme.shape, null);
+  });
+
+  testWidgets('Default BottomSheetThemeData debugFillProperties', (WidgetTester tester) async {
+    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
+    const BottomSheetThemeData().debugFillProperties(builder);
+
+    final List<String> description = builder.properties
+        .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
+        .map((DiagnosticsNode node) => node.toString())
+        .toList();
+
+    expect(description, <String>[]);
+  });
+
+  testWidgets('BottomSheetThemeData implements debugFillProperties', (WidgetTester tester) async {
+    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
+    BottomSheetThemeData(
+      backgroundColor: const Color(0xFFFFFFFF),
+      elevation: 2.0,
+      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2.0)),
+    ).debugFillProperties(builder);
+
+    final List<String> description = builder.properties
+        .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
+        .map((DiagnosticsNode node) => node.toString())
+        .toList();
+
+    expect(description, <String>[
+      'backgroundColor: Color(0xffffffff)',
+      'elevation: 2.0',
+      'shape: RoundedRectangleBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), BorderRadius.circular(2.0))',
+    ]);
+  });
+
+  testWidgets('Passing no BottomSheetThemeData returns defaults', (WidgetTester tester) async {
+    await tester.pumpWidget(MaterialApp(
+      home: Scaffold(
+        body: BottomSheet(
+          onClosing: () {},
+          builder: (BuildContext context) {
+            return Container();
+          },
+        ),
+      ),
+    ));
+
+    final Material material = tester.widget<Material>(
+      find.descendant(
+        of: find.byType(BottomSheet),
+        matching: find.byType(Material),
+      ).first,
+    );
+    expect(material.color, null);
+    expect(material.elevation, 0.0);
+    expect(material.shape, null);
+  });
+
+  testWidgets('BottomSheet uses values from BottomSheetThemeData', (WidgetTester tester) async {
+    final BottomSheetThemeData bottomSheetTheme = _bottomSheetTheme();
+
+    await tester.pumpWidget(MaterialApp(
+      theme: ThemeData(bottomSheetTheme: bottomSheetTheme),
+      home: Scaffold(
+        body: BottomSheet(
+          onClosing: () {},
+          builder: (BuildContext context) {
+            return Container();
+          },
+        ),
+      ),
+    ));
+
+    final Material material = tester.widget<Material>(
+      find.descendant(
+        of: find.byType(BottomSheet),
+        matching: find.byType(Material),
+      ).first,
+    );
+    expect(material.color, bottomSheetTheme.backgroundColor);
+    expect(material.elevation, bottomSheetTheme.elevation);
+    expect(material.shape, bottomSheetTheme.shape);
+  });
+
+  testWidgets('BottomSheet widget properties take priority over theme', (WidgetTester tester) async {
+    const Color backgroundColor = Colors.purple;
+    const double elevation = 7.0;
+    const ShapeBorder shape = RoundedRectangleBorder(
+      borderRadius: BorderRadius.all(Radius.circular(9.0)),
+    );
+
+    await tester.pumpWidget(MaterialApp(
+      theme: ThemeData(bottomSheetTheme: _bottomSheetTheme()),
+      home: Scaffold(
+        body: BottomSheet(
+          backgroundColor: backgroundColor,
+          elevation: elevation,
+          shape: shape,
+          onClosing: () {},
+          builder: (BuildContext context) {
+            return Container();
+          },
+        ),
+      ),
+    ));
+
+    final Material material = tester.widget<Material>(
+      find.descendant(
+        of: find.byType(BottomSheet),
+        matching: find.byType(Material),
+      ).first,
+    );
+    expect(material.color, backgroundColor);
+    expect(material.elevation, elevation);
+    expect(material.shape, shape);
+  });
+}
+
+BottomSheetThemeData _bottomSheetTheme() {
+  return BottomSheetThemeData(
+    backgroundColor: Colors.orange,
+    elevation: 12.0,
+    shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(12)),
+  );
+}
diff --git a/packages/flutter/test/material/modal_bottom_sheet_test.dart b/packages/flutter/test/material/modal_bottom_sheet_test.dart
index 1562cdf..406a152 100644
--- a/packages/flutter/test/material/modal_bottom_sheet_test.dart
+++ b/packages/flutter/test/material/modal_bottom_sheet_test.dart
@@ -251,6 +251,40 @@
     semantics.dispose();
   });
 
+  testWidgets('Verify that visual properties are passed through', (WidgetTester tester) async {
+    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
+    const Color color = Colors.pink;
+    const double elevation = 9.0;
+    final ShapeBorder shape = BeveledRectangleBorder(borderRadius: BorderRadius.circular(12));
+
+    await tester.pumpWidget(MaterialApp(
+      home: Scaffold(
+        key: scaffoldKey,
+        body: const Center(child: Text('body')),
+      ),
+    ));
+
+    showModalBottomSheet<void>(
+      context: scaffoldKey.currentContext,
+      backgroundColor: color,
+      elevation: elevation,
+      shape: shape,
+      builder: (BuildContext context) {
+        return Container(
+          child: const Text('BottomSheet'),
+        );
+      },
+    );
+
+    await tester.pump();
+    await tester.pump(const Duration(seconds: 1));
+
+    final BottomSheet bottomSheet = tester.widget(find.byType(BottomSheet));
+    expect(bottomSheet.backgroundColor, color);
+    expect(bottomSheet.elevation, elevation);
+    expect(bottomSheet.shape, shape);
+  });
+
   testWidgets('modal BottomSheet with scrollController has semantics', (WidgetTester tester) async {
     final SemanticsTester semantics = SemanticsTester(tester);
     final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
diff --git a/packages/flutter/test/material/persistent_bottom_sheet_test.dart b/packages/flutter/test/material/persistent_bottom_sheet_test.dart
index a2d3a43..21e0fad 100644
--- a/packages/flutter/test/material/persistent_bottom_sheet_test.dart
+++ b/packages/flutter/test/material/persistent_bottom_sheet_test.dart
@@ -426,6 +426,39 @@
     expect(find.byKey(bottomSheetKey), findsNothing);
   });
 
+  testWidgets('Verify that visual properties are passed through', (WidgetTester tester) async {
+    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
+    const Color color = Colors.pink;
+    const double elevation = 9.0;
+    final ShapeBorder shape = BeveledRectangleBorder(borderRadius: BorderRadius.circular(12));
+
+    await tester.pumpWidget(MaterialApp(
+      home: Scaffold(
+        key: scaffoldKey,
+        body: const Center(child: Text('body')),
+      ),
+    ));
+
+    scaffoldKey.currentState.showBottomSheet<void>((BuildContext context) {
+      return ListView(
+        shrinkWrap: true,
+        primary: false,
+        children: <Widget>[
+          Container(height: 100.0, child: const Text('One')),
+          Container(height: 100.0, child: const Text('Two')),
+          Container(height: 100.0, child: const Text('Three')),
+        ],
+      );
+    }, backgroundColor: color, elevation: elevation, shape: shape);
+
+    await tester.pumpAndSettle();
+
+    final BottomSheet bottomSheet = tester.widget(find.byType(BottomSheet));
+    expect(bottomSheet.backgroundColor, color);
+    expect(bottomSheet.elevation, elevation);
+    expect(bottomSheet.shape, shape);
+  });
+
   testWidgets('PersistentBottomSheetController.close dismisses the bottom sheet', (WidgetTester tester) async {
     final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
     await tester.pumpWidget(MaterialApp(