m3_expressive
Material 3 Expressive components for Flutter.
Google's Android 16 design language introduces a new generation of motion and shape work that Flutter's built-in widgets do not yet cover. This package fills that gap with five production-ready components that follow the M3 Expressive spec as closely as possible.
https://github.com/user-attachments/assets/c6f8a013-7280-4289-93a3-8429ee3e31b1
https://github.com/user-attachments/assets/0de34bf5-52b1-4c34-a87b-d2e29ee32c99
https://github.com/user-attachments/assets/f3274caa-fda7-48ff-814a-ad02a20396a1
https://github.com/user-attachments/assets/4dd2a67e-359a-4551-a4e1-b03724563b83
https://github.com/user-attachments/assets/b3e36f01-9606-4535-bcb7-540a6bd0fb52
Installation
dependencies:
m3_expressive: ^1.0.0
flutter pub get
Then import once in any file that needs it:
import 'package:m3_expressive/m3_expressive.dart';
Components
M3LoadingIndicator
A morphing square that cycles through four corner-radius configurations while
rotating. Drop-in replacement for CircularProgressIndicator.
// Defaults: size 48, color from Theme.colorScheme.primary
const M3LoadingIndicator()
// Custom
M3LoadingIndicator(
size: 32,
color: Colors.teal,
duration: const Duration(milliseconds: 1000),
)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
size |
double |
48 |
Width and height of the indicator |
color |
Color? |
colorScheme.primary |
Fill color |
duration |
Duration |
1400 ms |
Duration of one full morph cycle |
M3RefreshIndicator
Pull-to-refresh with a circular container that scales in from the top as the user pulls. Inside the circle, a shape morphs continuously through seven M3 Expressive forms using Catmull-Rom interpolation.
// Minimal usage
M3RefreshIndicator(
onRefresh: _handleRefresh,
child: ListView(...),
)
// Custom colors
M3RefreshIndicator(
onRefresh: _handleRefresh,
backgroundColor: Colors.indigo.shade100,
shapeColor: Colors.indigo,
child: ListView(...),
)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
onRefresh |
Future<void> Function() |
required | Called when refresh is triggered |
child |
Widget |
required | The scrollable content |
indicatorSize |
double |
56 |
Diameter of the indicator circle |
triggerDistance |
double |
80 |
Pull distance required to trigger |
backgroundColor |
Color? |
colorScheme.primaryContainer |
Circle background |
shapeColor |
Color? |
colorScheme.primary |
Shape fill color |
morphDuration |
Duration |
1600 ms |
Duration of one shape cycle while dragging |
The underlying painter is also public as M3ShapeMorphPainter for embedding
in your own CustomPaint.
M3DismissibleListItem
A list card with a morphing swipe-to-dismiss gesture. As the user drags horizontally, the card corners round toward a pill shape and a colored slab emerges from behind. The delete icon springs in past the halfway point. Releasing above the commit threshold (or with a fast fling) dismisses the item. Releasing below it snaps back with an elastic spring.
M3DismissibleListItem(
onDismissed: () => _deleteItem(item),
onTap: () => _openItem(item),
child: MyItemContent(item),
)
// Custom colors and icon
M3DismissibleListItem(
onDismissed: () => _archiveItem(item),
onTap: () => _openItem(item),
deleteColor: Colors.orange,
deleteIcon: Icons.archive_rounded,
cardColor: theme.colorScheme.primaryContainer,
child: MyItemContent(item),
)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
onDismissed |
VoidCallback |
required | Called when dismiss is committed |
onTap |
VoidCallback |
required | Called on tap |
child |
Widget |
required | Card content |
cardColor |
Color? |
colorScheme.surfaceContainerHighest |
Card background |
borderColor |
Color? |
same as cardColor |
Card border |
deleteColor |
Color? |
colorScheme.error |
Slab background at full reveal |
deleteOnColor |
Color? |
colorScheme.onError |
Icon color at full reveal |
deleteIcon |
IconData |
Symbols.delete_rounded |
Slab icon |
morphThreshold |
double |
72 |
Pixels to fully morph the radius |
commitThreshold |
double |
72 |
Pixels to commit the dismiss |
velocityThreshold |
double |
900 |
px/s to commit regardless of distance |
radiusMin |
double |
18 |
Corner radius at rest |
radiusMax |
double |
40 |
Corner radius at full morph |
M3UndoPill
A floating pill with a timed progress bar. Appears after a destructive action
and counts down until calling onComplete. The user can cancel at any time
by tapping the undo button or the dismiss icon.
Intended to be placed inside a Stack above your scrollable content.
Stack(
children: [
ListView(...),
M3UndoPill(
label: 'Item removed',
onComplete: () => _permanentlyDelete(item),
onCancel: () => _restoreItem(item),
),
],
)
// Custom
M3UndoPill(
label: 'Message deleted',
undoLabel: 'Restore',
duration: const Duration(seconds: 6),
accentColor: Colors.teal,
onComplete: _onComplete,
onCancel: _onCancel,
)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
label |
String |
required | Text describing the action |
onComplete |
VoidCallback |
required | Called when the timer expires |
onCancel |
VoidCallback |
required | Called when the user cancels |
undoLabel |
String |
'Undo' |
Undo button text |
duration |
Duration |
4 seconds |
Countdown duration |
backgroundColor |
Color? |
colorScheme.surfaceContainerLow |
Pill track color |
progressColor |
Color? |
colorScheme.surfaceContainerHighest |
Progress bar color |
accentColor |
Color? |
colorScheme.primary |
Undo button text color |
foregroundColor |
Color? |
colorScheme.onSurface |
Label and icon color |
dismissIcon |
IconData |
Symbols.close_rounded |
Dismiss button icon |
horizontalPadding |
double |
48 |
Inset from screen edges |
DraggableContainerButton and openContainerTransform
A Material 3 container transform transition with both tap and drag support.
DraggableContainerButton wraps any widget. On tap it immediately commits the navigation. On upward drag it follows the finger with a live morph preview painted in an Overlay above the navigation bar. Releasing above 40% of the trigger distance (or with a fast enough flick) navigates forward. Releasing below snaps back with a spring.
DraggableContainerButton(
closedColor: theme.colorScheme.primaryContainer,
closedRadius: BorderRadius.circular(28),
pageBuilder: (_) => const DetailPage(),
onReturn: _refreshData,
child: MyButton(),
)
openContainerTransform is the programmatic equivalent for cases where you manage the tap gesture yourself.
final _key = GlobalKey();
// On the source widget:
Container(key: _key, child: ...)
// On tap:
await openContainerTransform(
context: context,
sourceKey: _key,
closedColor: theme.colorScheme.primaryContainer,
closedRadius: BorderRadius.circular(28),
pageBuilder: (_) => const DetailPage(),
);
DraggableContainerButton parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
closedColor |
Color |
required | Button color in closed state |
closedRadius |
BorderRadius |
required | Button radii in closed state |
pageBuilder |
WidgetBuilder |
required | Builds the destination page |
child |
Widget |
required | Button content |
onReturn |
VoidCallback? |
null |
Called after the user returns |
triggerDistance |
double? |
35% screen height | Upward drag to reach t=1 |
onProgressUpdate |
void Function(double)? |
null |
Drag progress in 0, 1 |
onTapValidation |
Future<bool> Function()? |
null |
Async guard before tap commit |
onDragValidation |
bool Function()? |
null |
Sync guard before drag starts |
enableDrag |
bool |
true |
Enable or disable drag gesture |
openColor |
Color? |
colorScheme.surface |
Destination page background |
scrimColor |
Color? |
Colors.black |
Scrim color |
Theming
All color parameters default to values from the ambient Theme.of(context).colorScheme.
An app using standard Material 3 theming requires no color customization at all.
Dynamic color, dark mode, and custom color schemes are all handled automatically.
Requirements
Shape definitions provided by the material_shapes library by Kostiantyn Sokolovskyi, used under the MIT license.
- Flutter 3.10 or higher
- Dart 3.0 or higher
License
MIT. See LICENSE.
Libraries
- m3_expressive
- material_shapes
- A library for creating and morphing custom Material-style shapes with transformations and path conversion utilities for rendering.