expressive_m3
Material 3 Expressive design tokens, motion and widgets for Flutter.
A drop-in expressive layer on top of Material 3: emphasized easing + duration
tokens, the M3 spring matrix as native SpringDescriptions, the full
corner / spacing / elevation / type scales, the expressive polygon shapes, and a
small set of polished widgets — a shape-morphing loading indicator, a
pull-to-refresh that uses it, a wavy progress gauge, a spring press response and
shared-axis page transitions.
Every animated widget honours the platform reduce-motion setting.
Features
- Motion tokens —
ExpressiveMotion(M3 emphasized/standard easing + durations, with a reduced-motion gate). - Spring physics —
ExpressiveSpringScheme, the M3 spring matrix (spatial/effects × fast/default/slow) as Flutter-native springs, inexpressiveandstandardpresets; drive any controller withcontroller.springTo(target, spring). Swap the scheme per subtree withMotionSchemeScope. - Page motion —
ExpressivePageTransitionsBuilder(shared-axis X, no black flash),SpringPagePhysics(spring-settled paging),TabSwitchTransition(fade-through). - Design tokens —
ExpressiveRadius,ExpressiveSpacing,ExpressiveElevation(tonal),ExpressiveShapeTokens,ExpressiveTextTheme(M3 2021 type scale) andEmphasizedTextTheme(the M3 emphasized scale as aThemeExtension). - Shapes —
ExpressiveShape+ShapeClip/ExpressiveShapeBorderto clip or outline anything with the M3E polygons (cookie, sunny, clover, pill …). - Widgets —
ExpressiveLoadingIndicator,ExpressiveRefreshIndicator,CircularWavyProgress,PressableScale,ExpressiveLoader. - M3 Expressive components (not yet in Flutter) —
ExpressiveButtonGroup,ExpressiveSplitButton,ExpressiveFabMenu,ExpressiveToolbar(floating + docked),ExpressiveWavyLinearProgressandExpressiveWavyCircularProgress. - Haptics —
ExpressiveHaptics, a consistent selection/light/success/warning vocabulary.
Getting started
dependencies:
expressive_m3: ^0.1.0
import 'package:expressive_m3/expressive_m3.dart';
Usage
Theme — wire the tokens into ThemeData
final textTheme = ExpressiveTextTheme.base;
MaterialApp(
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
textTheme: textTheme,
pageTransitionsTheme: expressivePageTransitionsTheme,
extensions: [EmphasizedTextTheme.of(textTheme)],
),
home: const HomePage(),
);
Shape-morphing loading indicator
const ExpressiveLoadingIndicator(); // morphing glyph
const ExpressiveLoadingIndicator(contained: true); // on a tonal disc
Pull-to-refresh with the expressive indicator
ExpressiveRefreshIndicator(
onRefresh: () => controller.refresh(),
// edgeOffset: 56, // clear a pinned header inside a NestedScrollView
child: ListView(children: [...]),
);
Wavy progress gauge
CircularWavyProgress(
value: done / total,
label: '$done/$total',
// In a scroll list, give it a stable id so the sweep plays on first
// appearance / value increase — but not on every scroll re-mount.
animationId: 'job:$id',
);
Spring press response
PressableScale(
child: Card(child: ...),
);
Springs directly
class _S extends State<Foo> with SingleTickerProviderStateMixin {
late final _c = AnimationController.unbounded(vsync: this, value: 0);
void _open() =>
_c.springTo(1, MotionSchemeScope.of(context).spatialDefault);
}
Emphasized type + numeric identifiers
Text('DR87 NMJ', style: context.emphasizedText.titleLarge.numericId);
Expressive shapes
ShapeClip(
shape: ExpressiveShape.cookie,
child: ColoredBox(color: scheme.secondaryContainer),
);
Material(
shape: const ExpressiveShapeBorder(ExpressiveShape.sunny),
child: ...,
);
M3 Expressive components
// Connected button group.
ExpressiveButtonGroup(
children: [
FilledButton(onPressed: () {}, child: const Text('Day')),
FilledButton(onPressed: () {}, child: const Text('Week')),
FilledButton(onPressed: () {}, child: const Text('Month')),
],
);
// Split button — primary action + attached menu.
ExpressiveSplitButton(
label: const Text('Save'),
onPressed: () {},
menuChildren: [
MenuItemButton(onPressed: () {}, child: const Text('Save as draft')),
],
);
// FAB menu (drop into Scaffold.floatingActionButton).
ExpressiveFabMenu(
items: [
ExpressiveFabMenuItem(icon: const Icon(Icons.edit), label: 'Edit', onPressed: () {}),
ExpressiveFabMenuItem(icon: const Icon(Icons.share), label: 'Share', onPressed: () {}),
],
);
// Floating toolbar.
ExpressiveToolbar(
children: [
IconButton(onPressed: () {}, icon: const Icon(Icons.undo)),
IconButton(onPressed: () {}, icon: const Icon(Icons.redo)),
],
);
// Wavy progress — determinate + indeterminate.
const ExpressiveWavyLinearProgress(value: 0.6);
const ExpressiveWavyLinearProgress(); // indeterminate
const ExpressiveWavyCircularProgress(value: 0.65);
See the example/ app for a gallery of every token and widget.
Additional information
Contributions and issues are welcome on the repository. All animated widgets degrade to a static / instant state under the platform reduce-motion setting.
Libraries
- expressive_m3
- Material 3 Expressive design tokens, motion and widgets for Flutter.