Revert "[lib.widgets] Remove unused widgets."

This reverts commit 311b0e3a787e57f40313a9fc53a56c1994934ab2.

Reason for revert: Breaks build during global integration

Original change's description:
> [lib.widgets] Remove unused widgets.
> 
> Test: built and ran
> 
> Change-Id: Ic53608ad0429f076540a75341542c9ebc255bf2f

TBR=jamesr@google.com,anwilson@google.com,ejia@google.com

Change-Id: I1ed7adb90c47bdb5815573236c16eb10a4426b7c
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
diff --git a/bin/bluetooth_settings/lib/main.dart b/bin/bluetooth_settings/lib/main.dart
index da339e8..f72a9ac 100644
--- a/bin/bluetooth_settings/lib/main.dart
+++ b/bin/bluetooth_settings/lib/main.dart
@@ -4,7 +4,7 @@
 
 import 'package:flutter/material.dart';
 import 'package:lib.app.dart/logging.dart';
-import 'package:lib.widgets/model.dart';
+import 'package:lib.widgets/modular.dart';
 
 import 'src/bluetooth_model.dart';
 import 'src/bluetooth_settings.dart';
diff --git a/bin/display_settings/lib/main.dart b/bin/display_settings/lib/main.dart
index d6fbf94..a6db72a 100644
--- a/bin/display_settings/lib/main.dart
+++ b/bin/display_settings/lib/main.dart
@@ -3,10 +3,9 @@
 // found in the LICENSE file.
 
 import 'package:flutter/material.dart';
-import 'package:lib.app.dart/app.dart' show StartupContext;
 import 'package:lib.app.dart/logging.dart';
 import 'package:lib.display.flutter/display_policy_brightness_model.dart';
-import 'package:lib.widgets/model.dart';
+import 'package:lib.widgets/modular.dart';
 import 'src/widget.dart';
 
 /// Main entry point to the display settings module.
diff --git a/bin/userpicker_base_shell/lib/main.dart b/bin/userpicker_base_shell/lib/main.dart
index c57e8e5..2fadd48 100644
--- a/bin/userpicker_base_shell/lib/main.dart
+++ b/bin/userpicker_base_shell/lib/main.dart
@@ -8,10 +8,10 @@
 import 'package:flutter/material.dart';
 import 'package:lib.app.dart/app.dart';
 import 'package:lib.app.dart/logging.dart';
-import 'package:lib.base_shell/base_shell_widget.dart';
 import 'package:lib.base_shell/netstack_model.dart';
 import 'package:lib.widgets/application_deprecated.dart';
 import 'package:lib.widgets/model.dart';
+import 'package:lib.widgets/modular.dart';
 import 'package:meta/meta.dart';
 import 'package:zircon/zircon.dart';
 
diff --git a/bin/wifi_settings/lib/main.dart b/bin/wifi_settings/lib/main.dart
index 61fb059..51e6c8c 100644
--- a/bin/wifi_settings/lib/main.dart
+++ b/bin/wifi_settings/lib/main.dart
@@ -4,7 +4,7 @@
 
 import 'package:flutter/material.dart';
 import 'package:lib.app.dart/logging.dart';
-import 'package:lib.widgets/model.dart';
+import 'package:lib.widgets/modular.dart';
 
 import 'src/fuchsia/wifi_settings_model.dart';
 import 'src/wlan_manager.dart';
diff --git a/bin/xi/xi_session_demo/lib/main.dart b/bin/xi/xi_session_demo/lib/main.dart
index 4897794..cee62e0 100644
--- a/bin/xi/xi_session_demo/lib/main.dart
+++ b/bin/xi/xi_session_demo/lib/main.dart
@@ -4,7 +4,7 @@
 
 import 'package:flutter/material.dart';
 import 'package:lib.app.dart/logging.dart';
-import 'package:lib.widgets/model.dart';
+import 'package:lib.widgets/modular.dart';
 
 import 'src/demo_model.dart';
 import 'src/demo_widget.dart';
diff --git a/bin/xi/xi_session_demo/lib/src/demo_widget.dart b/bin/xi/xi_session_demo/lib/src/demo_widget.dart
index e9b78f2..1254193 100644
--- a/bin/xi/xi_session_demo/lib/src/demo_widget.dart
+++ b/bin/xi/xi_session_demo/lib/src/demo_widget.dart
@@ -4,7 +4,7 @@
 
 import 'package:flutter/material.dart';
 import 'package:lib.ui.flutter/child_view.dart';
-import 'package:lib.widgets/model.dart';
+import 'package:lib.widgets/modular.dart';
 
 import 'demo_model.dart';
 
diff --git a/examples/tictactoe/bin/tictactoe/game_board_mod/lib/main.dart b/examples/tictactoe/bin/tictactoe/game_board_mod/lib/main.dart
index c597242..a9ad760 100644
--- a/examples/tictactoe/bin/tictactoe/game_board_mod/lib/main.dart
+++ b/examples/tictactoe/bin/tictactoe/game_board_mod/lib/main.dart
@@ -10,7 +10,7 @@
 import 'package:lib.app.dart/logging.dart';
 import 'package:lib.app_driver.dart/module_driver.dart';
 import 'package:lib.proposal.dart/proposal.dart';
-import 'package:lib.widgets/model.dart';
+import 'package:lib.widgets/modular.dart';
 import 'package:tictactoe_common/common.dart';
 
 import 'src/model/tictactoe_model.dart';
diff --git a/examples/tictactoe/bin/tictactoe/game_board_mod/lib/src/widget/square.dart b/examples/tictactoe/bin/tictactoe/game_board_mod/lib/src/widget/square.dart
index 4524d37..1b04deb 100644
--- a/examples/tictactoe/bin/tictactoe/game_board_mod/lib/src/widget/square.dart
+++ b/examples/tictactoe/bin/tictactoe/game_board_mod/lib/src/widget/square.dart
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:flutter/material.dart';
-import 'package:lib.widgets/model.dart';
+import 'package:lib.widgets/modular.dart';
 import 'package:tictactoe_common/common.dart' show SquareState;
 
 import '../model/tictactoe_model.dart';
diff --git a/examples/tictactoe/bin/tictactoe/game_board_mod/lib/src/widget/tictactoe_board.dart b/examples/tictactoe/bin/tictactoe/game_board_mod/lib/src/widget/tictactoe_board.dart
index a12d692..0a48f65 100644
--- a/examples/tictactoe/bin/tictactoe/game_board_mod/lib/src/widget/tictactoe_board.dart
+++ b/examples/tictactoe/bin/tictactoe/game_board_mod/lib/src/widget/tictactoe_board.dart
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:flutter/material.dart';
-import 'package:lib.widgets/model.dart';
+import 'package:lib.widgets/modular.dart';
 import 'package:tictactoe_common/common.dart' show GameState;
 
 import '../model/tictactoe_model.dart';
diff --git a/examples/tictactoe/bin/tictactoe/game_board_mod/test/mod_test.dart b/examples/tictactoe/bin/tictactoe/game_board_mod/test/mod_test.dart
index dda8bd8..e85296a 100644
--- a/examples/tictactoe/bin/tictactoe/game_board_mod/test/mod_test.dart
+++ b/examples/tictactoe/bin/tictactoe/game_board_mod/test/mod_test.dart
@@ -1,6 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
-import 'package:lib.widgets/model.dart';
+import 'package:lib.widgets/modular.dart';
 import 'package:topaz.examples.tictactoe.bin.tictactoe.game_board_mod._tictactoe_game_board_mod_dart_library/src/model/tictactoe_model.dart'; // ignore: implementation_imports
 import 'package:topaz.examples.tictactoe.bin.tictactoe.game_board_mod._tictactoe_game_board_mod_dart_library/src/widget/square.dart'; // ignore: implementation_imports
 import 'package:topaz.examples.tictactoe.bin.tictactoe.game_board_mod._tictactoe_game_board_mod_dart_library/src/widget/tictactoe_board.dart'; // ignore: implementation_imports
diff --git a/examples/tictactoe/bin/tictactoe/scoreboard_mod/lib/main.dart b/examples/tictactoe/bin/tictactoe/scoreboard_mod/lib/main.dart
index e09a398..7bc0650 100644
--- a/examples/tictactoe/bin/tictactoe/scoreboard_mod/lib/main.dart
+++ b/examples/tictactoe/bin/tictactoe/scoreboard_mod/lib/main.dart
@@ -6,7 +6,7 @@
 import 'package:flutter/material.dart';
 import 'package:lib.app.dart/logging.dart';
 import 'package:lib.app_driver.dart/module_driver.dart';
-import 'package:lib.widgets/model.dart';
+import 'package:lib.widgets/modular.dart';
 import 'package:tictactoe_common/common.dart';
 
 import 'src/model/scoreboard_model.dart';
diff --git a/examples/tictactoe/bin/tictactoe/scoreboard_mod/lib/src/widget/scoreboard_widget.dart b/examples/tictactoe/bin/tictactoe/scoreboard_mod/lib/src/widget/scoreboard_widget.dart
index 90eb417..8913a87 100644
--- a/examples/tictactoe/bin/tictactoe/scoreboard_mod/lib/src/widget/scoreboard_widget.dart
+++ b/examples/tictactoe/bin/tictactoe/scoreboard_mod/lib/src/widget/scoreboard_widget.dart
@@ -1,5 +1,5 @@
 import 'package:flutter/material.dart';
-import 'package:lib.widgets/model.dart';
+import 'package:lib.widgets/modular.dart';
 
 import '../model/scoreboard_model.dart';
 
diff --git a/lib/base_shell/lib/base_model.dart b/lib/base_shell/lib/base_model.dart
index 958e179..a9a6ef0 100644
--- a/lib/base_shell/lib/base_model.dart
+++ b/lib/base_shell/lib/base_model.dart
@@ -17,10 +17,10 @@
 import 'package:lib.app.dart/app.dart' as app;
 import 'package:lib.app.dart/logging.dart';
 import 'package:lib.ui.flutter/child_view.dart';
+import 'package:lib.widgets/modular.dart';
 import 'package:meta/meta.dart';
 import 'package:zircon/zircon.dart' show Channel;
 
-import 'base_shell_model.dart';
 import 'netstack_model.dart';
 import 'user_manager.dart';
 
@@ -273,8 +273,7 @@
     super.onReady(userProvider, baseShellContext, presentation);
 
     final netstackProxy = NetstackProxy();
-    app.connectToService(
-        app.StartupContext.fromStartupInfo().environmentServices,
+    app.connectToService(StartupContext.fromStartupInfo().environmentServices,
         netstackProxy.ctrl);
     _netstackModel = NetstackModel(netstack: netstackProxy)..start();
 
diff --git a/public/dart/widgets/BUILD.gn b/public/dart/widgets/BUILD.gn
index 10a37d9..7de0643 100644
--- a/public/dart/widgets/BUILD.gn
+++ b/public/dart/widgets/BUILD.gn
@@ -20,12 +20,17 @@
     "src/model/spring_model.dart",
     "src/model/ticking_model.dart",
     "src/model/tracing_spring_model.dart",
+    "src/modular/base_shell_model.dart",
+    "src/modular/base_shell_widget.dart",
     "src/modular/dank_session_shell_widget.dart",
     "src/widgets/alphatar.dart",
     "src/widgets/conditional_builder.dart",
     "src/widgets/fuchsia_spinner.dart",
     "src/widgets/future_widget.dart",
+    "src/widgets/mondrian_spinner.dart",
     "src/widgets/rk4_spring_simulation.dart",
+    "src/widgets/shadowed_text.dart",
+    "src/widgets/text_placeholder.dart",
     "src/widgets/ticking_state.dart",
     "src/widgets/window_media_query.dart",
     "widgets.dart",
diff --git a/public/dart/widgets/lib/modular.dart b/public/dart/widgets/lib/modular.dart
index 056442e..e26b2de 100644
--- a/public/dart/widgets/lib/modular.dart
+++ b/public/dart/widgets/lib/modular.dart
@@ -5,4 +5,6 @@
 // WARNING(MS-1613): All Fuchsia system dependencies will be separated out from
 // this Flutter specific Dart package. If you need Fuchsia system interactions
 // use //topaz/public/lib/app_driver/dart or the Dart FIDL Bindings directly.
+export 'src/modular/base_shell_model.dart';
+export 'src/modular/base_shell_widget.dart';
 export 'src/modular/dank_session_shell_widget.dart';
diff --git a/public/dart/widgets/lib/src/modular/base_shell_model.dart b/public/dart/widgets/lib/src/modular/base_shell_model.dart
new file mode 100644
index 0000000..628bec1
--- /dev/null
+++ b/public/dart/widgets/lib/src/modular/base_shell_model.dart
@@ -0,0 +1,43 @@
+// Copyright 2017 The Fuchsia 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:fidl_fuchsia_modular/fidl.dart';
+import 'package:fidl_fuchsia_ui_policy/fidl.dart';
+import 'package:lib.widgets/model.dart';
+import 'package:meta/meta.dart';
+
+export 'package:lib.widgets/model.dart' show ScopedModel, ScopedModelDescendant;
+
+/// The [Model] that provides a [BaseShellContext] and [UserProvider].
+class BaseShellModel extends Model {
+  BaseShellContext _baseShellContext;
+  UserProvider _userProvider;
+  Presentation _presentation;
+
+  /// The [BaseShellContext] given to this app's [BaseShell].
+  BaseShellContext get baseShellContext => _baseShellContext;
+
+  /// The [UserProvider] given to this app's [BaseShell].
+  UserProvider get userProvider => _userProvider;
+
+  /// The [Presentation] given to this app's [BaseShell].
+  Presentation get presentation => _presentation;
+
+  /// Called when this app's [BaseShell] is given its [BaseShellContext],
+  /// and [UserProvider], and (optionally) its [Presentation].
+  @mustCallSuper
+  void onReady(
+    UserProvider userProvider,
+    BaseShellContext baseShellContext,
+    Presentation presentation,
+  ) {
+    _userProvider = userProvider;
+    _baseShellContext = baseShellContext;
+    _presentation = presentation;
+    notifyListeners();
+  }
+
+  /// Called when the app's [BaseShell] stops.
+  void onStop() => null;
+}
diff --git a/public/dart/widgets/lib/src/modular/base_shell_widget.dart b/public/dart/widgets/lib/src/modular/base_shell_widget.dart
new file mode 100644
index 0000000..1b90793
--- /dev/null
+++ b/public/dart/widgets/lib/src/modular/base_shell_widget.dart
@@ -0,0 +1,103 @@
+// Copyright 2017 The Fuchsia 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:lib.app.dart/app.dart';
+import 'package:fidl_fuchsia_auth/fidl.dart';
+import 'package:fidl_fuchsia_modular/fidl.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:fidl/fidl.dart';
+import 'package:lib.device.dart/device.dart';
+import 'package:meta/meta.dart';
+
+import '../widgets/window_media_query.dart';
+import 'base_shell_model.dart';
+
+/// A wrapper widget intended to be the root of the application that is
+/// a [BaseShell].  Its main purpose is to hold the [StartupContext] and
+/// [BaseShell] instances so they aren't garbage collected.
+/// For convenience, [advertise] does the advertising of the app as a
+/// [BaseShell] to the rest of the system via the [StartupContext].
+/// Also for convienence, the [BaseShellModel] given to this widget
+/// will be made available to [child] and [child]'s descendants.
+class BaseShellWidget<T extends BaseShellModel> extends StatelessWidget {
+  /// The [StartupContext] to [advertise] its [BaseShell] services to.
+  final StartupContext startupContext;
+
+  /// The bindings for the [BaseShell] service implemented by [BaseShellImpl].
+  final Set<BaseShellBinding> _baseShellBindingSet =
+      new Set<BaseShellBinding>();
+
+  /// The bindings for the [Lifecycle] service implemented by [BaseShellImpl].
+  final Set<LifecycleBinding> _lifecycleBindingSet =
+      new Set<LifecycleBinding>();
+
+  /// The [BaseShell] to [advertise].
+  final BaseShellImpl _baseShell;
+
+  /// The rest of the application.
+  final Widget child;
+
+  final T _baseShellModel;
+
+  /// Constructor.
+  BaseShellWidget({
+    @required this.startupContext,
+    T baseShellModel,
+    AuthenticationUiContext authenticationUiContext,
+    this.child,
+  })  : _baseShellModel = baseShellModel,
+        _baseShell = _createBaseShell(
+          baseShellModel,
+          authenticationUiContext,
+        );
+
+  @override
+  Widget build(BuildContext context) => new MaterialApp(
+        home: new Material(
+          child: new Directionality(
+            textDirection: TextDirection.ltr,
+            child: new WindowMediaQuery(
+              child: _baseShellModel == null
+                  ? child
+                  : new ScopedModel<T>(model: _baseShellModel, child: child),
+            ),
+          ),
+        ),
+      );
+
+  /// Advertises [_baseShell] as a [BaseShell] to the rest of the system via
+  /// the [StartupContext].
+  void advertise() {
+    startupContext.outgoingServices
+      ..addServiceForName((InterfaceRequest<BaseShell> request) {
+        BaseShellBinding binding = new BaseShellBinding()
+          ..bind(_baseShell, request);
+        _baseShellBindingSet.add(binding);
+      }, BaseShell.$serviceName)
+      ..addServiceForName((InterfaceRequest<Lifecycle> request) {
+        LifecycleBinding binding = new LifecycleBinding()
+          ..bind(_baseShell, request);
+        _lifecycleBindingSet.add(binding);
+      }, Lifecycle.$serviceName);
+  }
+
+  static BaseShell _createBaseShell(
+    BaseShellModel baseShellModel,
+    AuthenticationUiContext authenticationUiContext,
+  ) {
+    return new BaseShellImpl(
+      authenticationUiContext: authenticationUiContext,
+      onReady: baseShellModel?.onReady,
+      onStop: () {
+        baseShellModel?.onStop?.call();
+      },
+    );
+  }
+
+  /// Cancels any authentication flow currently in progress.
+  void cancelAuthenticationFlow() {
+    _baseShell.closeAuthenticationUiContextBindings();
+  }
+}
diff --git a/public/dart/widgets/lib/src/widgets/mondrian_spinner.dart b/public/dart/widgets/lib/src/widgets/mondrian_spinner.dart
new file mode 100644
index 0000000..17dd489
--- /dev/null
+++ b/public/dart/widgets/lib/src/widgets/mondrian_spinner.dart
@@ -0,0 +1,177 @@
+// Copyright 2017 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:async';
+import 'dart:math' as math;
+
+import 'package:flutter/material.dart';
+
+const double _kBorderWidth = 4.0;
+const double _kBorderRadius = 8.0;
+const double _kClipRadius = 12.0;
+
+const int _kBlueStartingIndex = 0;
+const int _kYellowStartingIndex = 3;
+
+/// The mondrian spinner.  A series of overlapping rectangular elements that
+/// rotate.  The parent of this [MondrianSpinner] must have non-infinite bounds.
+/// The [MondrianSpinner] will center itself in its parent and have a diameter
+/// equal to the minimum dimension.
+class MondrianSpinner extends StatefulWidget {
+  @override
+  _MondrianSpinnerState createState() => new _MondrianSpinnerState();
+}
+
+class _MondrianSpinnerState extends State<MondrianSpinner> {
+  Timer _timer;
+  int _index = 0;
+  List<Rect> _positions;
+
+  @override
+  void initState() {
+    super.initState();
+    _timer = new Timer.periodic(
+      const Duration(milliseconds: 500),
+      (_) => setState(() {
+            _index = (_index + 1) % _positions.length;
+          }),
+    );
+  }
+
+  @override
+  void dispose() {
+    _timer?.cancel();
+    _timer = null;
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) => new LayoutBuilder(
+        builder: (BuildContext context, BoxConstraints constraints) {
+          double diameter = math.min(
+            constraints.maxWidth,
+            constraints.maxHeight,
+          );
+          _positions = <Rect>[
+            new Rect.fromLTWH(
+              0.0,
+              0.0,
+              diameter / 2.0 + _kBorderWidth / 2.0,
+              diameter,
+            ),
+            new Rect.fromLTWH(
+              0.0,
+              diameter / 2.0 - _kBorderWidth / 2.0,
+              diameter / 2.0 + _kBorderWidth / 2.0,
+              diameter / 2.0 + _kBorderWidth / 2.0,
+            ),
+            new Rect.fromLTWH(
+              0.0,
+              diameter / 2.0 - _kBorderWidth / 2.0,
+              diameter,
+              diameter / 2.0 + _kBorderWidth / 2.0,
+            ),
+            new Rect.fromLTWH(
+              diameter / 2.0 - _kBorderWidth / 2.0,
+              diameter / 2.0 - _kBorderWidth / 2.0,
+              diameter / 2.0 + _kBorderWidth / 2.0,
+              diameter / 2.0 + _kBorderWidth / 2.0,
+            ),
+            new Rect.fromLTWH(
+              diameter / 2.0 - _kBorderWidth / 2.0,
+              0.0,
+              diameter / 2.0 + _kBorderWidth / 2.0,
+              diameter,
+            ),
+            new Rect.fromLTWH(
+              diameter / 2.0 - _kBorderWidth / 2.0,
+              0.0,
+              diameter / 2.0 + _kBorderWidth / 2.0,
+              diameter / 2.0 + _kBorderWidth / 2.0,
+            ),
+            new Rect.fromLTWH(
+              0.0,
+              0.0,
+              diameter,
+              diameter / 2.0 + _kBorderWidth / 2.0,
+            ),
+            new Rect.fromLTWH(
+              0.0,
+              0.0,
+              diameter / 2.0 + _kBorderWidth / 2.0,
+              diameter / 2.0 + _kBorderWidth / 2.0,
+            ),
+          ];
+
+          return new Center(
+            child: new Container(
+              width: diameter,
+              height: diameter,
+              decoration: new BoxDecoration(
+                color: Colors.black,
+                borderRadius: new BorderRadius.circular(_kBorderRadius),
+              ),
+              foregroundDecoration: new BoxDecoration(
+                border: new Border.all(
+                  color: Colors.black,
+                  width: _kBorderWidth,
+                ),
+                borderRadius: new BorderRadius.circular(_kBorderRadius),
+              ),
+              child: new ClipRRect(
+                borderRadius: new BorderRadius.circular(_kClipRadius),
+                child: new Stack(
+                  fit: StackFit.passthrough,
+                  children: <Widget>[
+                    new Container(
+                      margin: const EdgeInsets.all(_kBorderWidth),
+                      color: Colors.red[700],
+                    ),
+                    new AnimatedPositioned(
+                      left: _yellowPosition.left,
+                      top: _yellowPosition.top,
+                      width: _yellowPosition.width,
+                      height: _yellowPosition.height,
+                      curve: Curves.fastOutSlowIn,
+                      duration: const Duration(milliseconds: 250),
+                      child: new Container(
+                        color: Colors.yellow[700],
+                        foregroundDecoration: new BoxDecoration(
+                          border: new Border.all(
+                            color: Colors.black,
+                            width: _kBorderWidth,
+                          ),
+                        ),
+                      ),
+                    ),
+                    new AnimatedPositioned(
+                      left: _bluePosition.left,
+                      top: _bluePosition.top,
+                      width: _bluePosition.width,
+                      height: _bluePosition.height,
+                      curve: Curves.fastOutSlowIn,
+                      duration: const Duration(milliseconds: 250),
+                      child: new Container(
+                        color: Colors.blue[700],
+                        foregroundDecoration: new BoxDecoration(
+                          border: new Border.all(
+                            color: Colors.black,
+                            width: _kBorderWidth,
+                          ),
+                        ),
+                      ),
+                    )
+                  ],
+                ),
+              ),
+            ),
+          );
+        },
+      );
+
+  Rect get _bluePosition =>
+      _positions[(_index + _kBlueStartingIndex) % _positions.length];
+  Rect get _yellowPosition =>
+      _positions[(_index + _kYellowStartingIndex) % _positions.length];
+}
diff --git a/public/dart/widgets/lib/src/widgets/shadowed_text.dart b/public/dart/widgets/lib/src/widgets/shadowed_text.dart
new file mode 100644
index 0000000..fc552f1
--- /dev/null
+++ b/public/dart/widgets/lib/src/widgets/shadowed_text.dart
@@ -0,0 +1,51 @@
+// Copyright 2018 The Fuchsia 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:meta/meta.dart';
+
+/// Function that returns a stylized widget with the specified text and
+/// color.
+typedef TextBuilder = Widget Function(
+    BuildContext context, String text, Color color);
+
+/// Text overlaid over another copy of text with shadow color.
+class ShadowedText extends StatelessWidget {
+  /// Function that returns the text widget
+  final TextBuilder textBuilder;
+
+  /// Text to display
+  final String text;
+
+  /// Shadow color
+  final Color shadowColor;
+
+  /// Text color
+  final Color textColor;
+
+  /// Constructor
+  const ShadowedText({
+    @required this.textBuilder,
+    @required this.text,
+    @required this.shadowColor,
+    @required this.textColor,
+  })  : assert(textBuilder != null),
+        assert(text != null),
+        assert(shadowColor != null),
+        assert(textColor != null);
+
+  @override
+  Widget build(BuildContext context) {
+    return new Stack(
+      fit: StackFit.passthrough,
+      children: <Widget>[
+        new Transform(
+          transform: new Matrix4.translationValues(2.0, 2.0, 0.0),
+          child: textBuilder(context, text, shadowColor),
+        ),
+        textBuilder(context, text, textColor),
+      ],
+    );
+  }
+}
diff --git a/public/dart/widgets/lib/src/widgets/text_placeholder.dart b/public/dart/widgets/lib/src/widgets/text_placeholder.dart
new file mode 100644
index 0000000..9a4db77
--- /dev/null
+++ b/public/dart/widgets/lib/src/widgets/text_placeholder.dart
@@ -0,0 +1,35 @@
+// Copyright 2017 The Fuchsia 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:meta/meta.dart';
+
+final Color _kPlaceholderColor = Colors.grey[300];
+
+/// Renders a text placeholder meant for loading/scaffolding
+class TextPlaceholder extends StatelessWidget {
+  /// Constructor
+  const TextPlaceholder({
+    @required this.style,
+    this.width = 80.0,
+  }) : assert(style != null);
+
+  /// Style of the text that this placeholder is used for
+  final TextStyle style;
+
+  /// Width that this placeholder should take up
+  final double width;
+
+  @override
+  Widget build(BuildContext context) {
+    return new Container(
+      margin: new EdgeInsets.symmetric(
+        vertical: (style.fontSize * style.height - style.fontSize) / 2.0,
+      ),
+      color: _kPlaceholderColor,
+      height: style.fontSize,
+      width: width,
+    );
+  }
+}
diff --git a/public/dart/widgets/lib/widgets.dart b/public/dart/widgets/lib/widgets.dart
index 2897c7b..ac66aeb 100644
--- a/public/dart/widgets/lib/widgets.dart
+++ b/public/dart/widgets/lib/widgets.dart
@@ -12,7 +12,10 @@
 export 'src/widgets/fuchsia_spinner.dart';
 export 'src/widgets/future_widget.dart';
 export 'src/widgets/lottie_player.dart';
+export 'src/widgets/mondrian_spinner.dart';
 export 'src/widgets/rk4_spring_simulation.dart';
 export 'src/widgets/screen_container.dart';
+export 'src/widgets/shadowed_text.dart';
+export 'src/widgets/text_placeholder.dart';
 export 'src/widgets/ticking_state.dart';
 export 'src/widgets/window_media_query.dart';