Material 3 Expressive
A faithful Flutter implementation of the Material 3 Expressive component set.
Every widget is exposed as a direct M3E* class with spring-driven press
feedback, shape morphing, and hover/focus/press state layers. Design tokens
(color, typography, motion, shapes, elevation) are provided through
M3ETheme.
Runtime dependencies are intentionally small — see
Dependencies for the packages declared in
pubspec.yaml.
Samples
| Actions | Selection |
|---|---|
![]() |
![]() |
| Containment | Navigation |
|---|---|
![]() |
![]() |
| Feedback |
|---|
![]() |
Example app
An interactive gallery demonstrating all 44 widgets lives in the
example/ directory. It groups components the same way as the
official Material 3 catalog:
| Tab | Page | Components |
|---|---|---|
| Do | actions_page.dart |
Buttons, FABs, groups, toggles, segmented & split buttons |
| Pick | selection_page.dart |
Checkbox, radio, switch, chips, dropdown, slider (incl. wavy), pickers |
| View | containment_page.dart |
Cards, carousel, lists, divider, dialogs, sheets |
| Nav | navigation_page.dart |
App bars (incl. search), tabs, nav bar/rail/drawer, toolbar, menu |
| Find | feedback_page.dart |
Badges, progress, refresh, tooltip, snackbar, inputs |
The gallery shell in example/lib/main.dart uses
M3EMaterialApp with adaptive theming and a light/dark toggle.
cd example
flutter run
Features
- 44 widgets across 39 component modules, covering Actions, Selection, Containment, Navigation, and Feedback (communication + text input).
- Direct component API — construct each
M3E*widget directly; enums and models are exported from a single library import. - Expressive motion & interaction — spring physics (via
motor), shape morphing, liquid selection indicators, shared haptics (M3EHaptics), and proper state layers on every interactive surface. - Design token foundations — color schemes, typography, motion, shapes
(including
material_new_shapesmorph polygons), elevation, haptics, and state layers via theM3EThemeinherited widget. - Interactive example gallery in
example/with live demos for every component.
Requirements
| Tool | Version |
|---|---|
| Flutter | >= 3.3.0 |
| Dart | ^3.12.0 |
Installation
Add the package to your pubspec.yaml:
dependencies:
material_3_expressive: ^1.0.4
Then fetch it:
flutter pub get
Or add it from the command line:
flutter pub add material_3_expressive
Dependencies
External packages declared in pubspec.yaml:
| Package | Role in this library |
|---|---|
flutter |
SDK — widgets, painting, gestures, and Material primitives used throughout |
collection |
Small collection helpers used by component logic |
dynamic_color |
Platform dynamic / Material You seed colors for M3EMaterialApp (dynamicColoring) |
motor |
Unified motion API — physics springs and curves that drive expressive morphs and liquid selection indicators |
material_new_shapes |
Expressive RoundedPolygon morph shapes (M3EMaterialNewShapes) used by loading / shape-driven surfaces |
Dev-only: flutter_lints, flutter_test, and custom_lint.
Quick start
Import the library — a single import exposes every component and foundation:
import 'package:material_3_expressive/material_3_expressive.dart';
Recommended: M3EMaterialApp
Wrap your app in M3EMaterialApp for adaptive theming, dynamic color, and
Material ThemeMode alignment (same pattern as the example gallery):
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return M3EMaterialApp(
title: 'My App',
data: M3EThemeData.light(seedColor: const Color(0xFF6750A4)),
autoTheming: true,
dynamicColoring: true,
drawUnderSystemBars: true, // transparent system bars, edge-to-edge layout
home: const HomePage(),
);
}
}
Surfaces draw behind the OS navigation bar; components such as M3ENavigationBar
keep interactive content above the gesture area via viewPadding.
Alternative: M3ETheme subtree
If you already have an app shell, wrap any subtree in M3ETheme:
M3ETheme(
data: M3EThemeData.light(seedColor: const Color(0xFF6750A4)),
child: myApp,
);
Use M3EThemeData.dark(...) for a dark scheme. If no M3ETheme is found,
components fall back to a default light theme.
Accessing theme tokens
final theme = M3ETheme.of(context);
final scheme = theme.colorScheme;
final type = theme.typeScale;
// Toggle brightness at runtime (requires adaptive M3EMaterialApp / M3EThemeScope)
M3ETheme.controllerOf(context)?.toggleBrightness(
fallback: theme.brightness,
autoTheming: true,
);
Theming
M3EThemeData bundles expressive tokens and per-component themes:
final theme = M3EThemeData.light(
seedColor: const Color(0xFF6750A4),
);
// Override a single component theme
final custom = theme.copyWith(
buttonTheme: M3EButtonTheme.defaults.copyWith(/* ... */),
);
Key properties on M3EThemeData:
colorScheme—M3EColorSchemewith M3 semantic rolestypeScale—M3ETypeScale(display, headline, title, label, body)spacing,visualDensity, per-component*Themeextensions
M3EMaterialApp additionally supports autoTheming (platform brightness) and
dynamicColoring (OS seed color on supported platforms — Material You primary
on Android 12+, accent color on desktop — with schemes generated via
ColorScheme.fromSeed).
Components
Every component is a widget you construct directly. Snippets below use a single
import. Stateful controls show // in State where a setState wrapper is
needed.
Actions
See also:
example/lib/pages/actions_page.dart(Do tab)
M3EButton
Five color variants with shape morphing on press.
M3EButton(
style: M3EButtonStyle.elevated,
onPressed: () {},
child: const Text('Elevated'),
);
M3EButton.icon(
icon: const Icon(M3EIcons.add),
label: const Text('Add'),
onPressed: () {},
);
M3EIconButton
Icon-only actions; supports toggle selection. Optional visualSize overrides
the painted control size while hit target follows theme rules.
M3EIconButton(
icon: const Icon(M3EIcons.edit),
variant: M3EIconButtonVariant.filled,
onPressed: () {},
);
// in State
M3EIconButton(
icon: const Icon(M3EIcons.add),
selectedIcon: const Icon(M3EIcons.check),
isSelected: isFavorite,
onPressed: () => setState(() => isFavorite = !isFavorite),
);
M3EFab
Floating action button in three sizes.
M3EFab(
icon: const Icon(M3EIcons.add),
size: M3EFabSize.large,
color: M3EFabColor.tertiary,
onPressed: () {},
);
M3EExtendedFab
FAB with a text label.
M3EExtendedFab(
label: 'Compose',
icon: const Icon(M3EIcons.edit),
onPressed: () {},
);
M3EFabMenu
Speed-dial menu anchored to a FAB. The FAB morphs size (80↔56) and circle when
opening; use position for left/right anchor, and optional expandIcon /
collapseIcon (fallbacks: icon / closeIcon).
M3EFabMenu(
position: M3EFabMenuPosition.right,
expandIcon: const Icon(M3EIcons.add),
collapseIcon: const Icon(M3EIcons.close),
items: [
M3EFabMenuItem(
icon: const Icon(M3EIcons.edit),
label: 'Note',
onPressed: () {},
),
M3EFabMenuItem(
icon: const Icon(M3EIcons.schedule),
label: 'Reminder',
onPressed: () {},
),
],
);
M3EButtonGroup
Grouped icon buttons with neighbour squish or connected corner morphing.
// in State
M3EButtonGroup(
selectedIndex: groupIndex,
onSelectedIndexChanged: (i) => setState(() => groupIndex = i ?? groupIndex),
actions: const [
M3EButtonGroupAction(icon: Icon(M3EIcons.arrow_back)),
M3EButtonGroupAction(icon: Icon(M3EIcons.add)),
M3EButtonGroupAction(icon: Icon(M3EIcons.arrow_forward)),
],
);
M3EButtonGroup(
type: M3EButtonGroupType.connected,
actions: const [
M3EButtonGroupAction(icon: Icon(M3EIcons.chevron_left)),
M3EButtonGroupAction(icon: Icon(M3EIcons.menu)),
M3EButtonGroupAction(icon: Icon(M3EIcons.chevron_right)),
],
);
M3EToggleButton
Toggle with round-to-square shape morphing.
// in State
M3EToggleButton.filled(
icon: const Icon(M3EIcons.favorite_border),
checkedIcon: const Icon(M3EIcons.favorite),
checked: isFavorite,
onCheckedChange: (v) => setState(() => isFavorite = v),
);
M3EToggleButton.text(
label: const Text('Bold'),
checked: isBold,
onCheckedChange: (v) => setState(() => isBold = v),
);
M3ESegmentedButton
Single- or multi-select segmented control.
// in State — single select
M3ESegmentedButton<String>(
segments: const [
M3ESegment(value: 'list', label: 'List'),
M3ESegment(value: 'grid', label: 'Grid'),
],
selected: viewMode,
onSelectionChanged: (v) => setState(() => viewMode = v),
);
// multi select
M3ESegmentedButton<String>(
multiSelect: true,
segments: const [
M3ESegment(value: 'new', label: 'New'),
M3ESegment(value: 'sale', label: 'Sale'),
],
selected: filters,
onSelectionChanged: (v) => setState(() => filters = v),
);
M3ESplitButton
Primary action with a trailing menu.
M3ESplitButton<String>(
label: 'Save',
leadingIcon: M3EIcons.check,
onPressed: () {},
onSelected: (value) {},
items: const [
M3ESplitButtonItem(value: 'draft', child: Text('Save as draft')),
M3ESplitButtonItem(value: 'copy', child: Text('Save a copy')),
],
);
Selection
See also:
example/lib/pages/selection_page.dart(Pick tab)
M3ECheckbox
Binary and tristate checkbox.
// in State
M3ECheckbox(
value: checked,
onChanged: (v) => setState(() => checked = v),
);
M3ECheckbox(
value: tristateValue,
tristate: true,
onChanged: (v) => setState(() => tristateValue = v),
);
M3ERadio
Mutually exclusive selection within a group. Optional label is part of the
tap target.
// in State
M3ERadio<String>(
value: 'pro',
groupValue: plan,
label: const Text('Pro'),
onChanged: (v) => setState(() => plan = v),
);
M3ESwitch
On/off toggle with optional selected icon. Hover/focus/press paints a
thumb-centered translucent state layer (stateLayerSize, default 48 via theme).
// in State
M3ESwitch(
value: wifiEnabled,
selectedIcon: const Icon(M3EIcons.check),
onChanged: (v) => setState(() => wifiEnabled = v),
);
M3ESwitch(
value: bluetoothEnabled,
stateLayerSize: 56,
onChanged: (v) => setState(() => bluetoothEnabled = v),
);
M3EChip
Assist, filter, input, and suggestion chip types.
M3EChip(
label: 'Assist',
leading: const Icon(M3EIcons.edit),
onPressed: () {},
);
// in State — filter chip
M3EChip(
label: 'Flutter',
type: M3EChipType.filter,
selected: chips.contains('flutter'),
onPressed: () => toggleChip('flutter'),
);
M3EDropdownMenu
Static list, multi-select, search, and async loading.
// Single select
M3EDropdownMenu<String>(
singleSelect: true,
items: const [
M3EDropdownItem(label: 'Flutter', value: 'flutter'),
M3EDropdownItem(label: 'Dart', value: 'dart'),
],
fieldStyle: const M3EDropdownFieldStyle(hintText: 'Choose a framework'),
onSelectionChanged: (items) {},
);
// Multi select with search
M3EDropdownMenu<String>(
searchEnabled: true,
items: const [
M3EDropdownItem(label: 'Layout', value: 'layout'),
M3EDropdownItem(label: 'Theming', value: 'theming'),
],
fieldStyle: const M3EDropdownFieldStyle(hintText: 'Select skills'),
onSelectionChanged: (items) {},
);
// Async items
M3EDropdownMenu<String>.future(
singleSelect: true,
future: () async => [
const M3EDropdownItem(label: 'Ghana', value: 'gh'),
const M3EDropdownItem(label: 'Kenya', value: 'ke'),
],
fieldStyle: const M3EDropdownFieldStyle(hintText: 'Load countries'),
onSelectionChanged: (items) {},
);
M3ESlider
Compose Material 3 expressive slider — standard, centered, wavy, vertical, and
range. Optional trackThickness, cornerRadius, thumbLength, dotSize,
dotSpacing, and dotBuilder customize track, thumb, and stop/tick markers.
cornerRadius defaults to theme trackCornerRadius (8) and is not derived
from track thickness.
// in State
M3ESlider(
value: volume,
onChanged: (v) => setState(() => volume = v),
);
M3ESlider(
value: brightness,
max: 5,
divisions: 5,
onChanged: (v) => setState(() => brightness = v),
);
// Wavy active value (inactive track stays flat)
M3ESlider.wavy(
value: progress,
onChanged: (v) => setState(() => progress = v),
);
M3ESlider.centered(
value: balance,
min: -100,
max: 100,
onChanged: (v) => setState(() => balance = v),
);
// Custom track / thumb / end dots (size & edge padding)
M3ESlider(
value: level,
max: 4,
divisions: 4,
trackThickness: 30,
cornerRadius: 8,
thumbLength: 50,
dotSize: 12,
dotSpacing: 10,
onChanged: (v) => setState(() => level = v),
dotBuilder: ({
required context,
required color,
required size,
required active,
}) {
// e.g. paint M3EMaterialNewShapes.cookie4Sided / softBurst
return ColoredBox(color: color);
},
);
M3ERangeSlider(
values: range,
onChanged: (v) => setState(() => range = v),
);
M3ERangeSlider.wavy(
values: range,
onChanged: (v) => setState(() => range = v),
);
SizedBox(
height: 160,
width: 48,
child: M3ESlider.vertical(
value: level,
onChanged: (v) => setState(() => level = v),
),
);
M3EDatePicker
Dialog and inline calendar date pickers.
// Inline calendar
M3ECalendarDatePicker(
initialDate: date,
firstDate: DateTime(2020),
lastDate: DateTime(2030),
onDateChanged: (v) => setState(() => date = v),
);
// Dialog
final picked = await M3EDatePicker.show(
context,
initialDate: date,
firstDate: DateTime(2020),
lastDate: DateTime(2030),
);
// Range dialog
final range = await M3EDatePicker.showRange(
context,
firstDate: DateTime(2020),
lastDate: DateTime(2030),
);
M3ETimePicker
Dialog and dial-style time picker.
// Dialog
final M3ETime? picked = await M3ETimePicker.show(
context,
initialTime: time,
);
// Inline dial
M3EDialTimePicker(
value: time,
onChanged: (v) => setState(() => time = v),
);
Containment
See also:
example/lib/pages/containment_page.dart(View tab)
M3ECard
Elevated, filled, and outlined surface for content and actions.
M3ECard(child: const Text('Elevated'));
M3ECard(
variant: M3ECardVariant.filled,
child: const Text('Filled'),
);
M3ECard(
variant: M3ECardVariant.outlined,
onPressed: () {},
child: const Text('Outlined (tap)'),
);
M3ECarousel
Hero, contained, and uncontained layouts — horizontal by default, or vertical
via axis.
M3ECarousel(
type: M3ECarouselType.hero,
heroAlignment: M3ECarouselHeroAlignment.center,
onTap: (index) {},
children: List.generate(10, (i) => ColoredBox(color: Colors.blue)),
);
M3ECarousel(
type: M3ECarouselType.contained,
isExtended: true,
children: items,
);
M3ECarousel(
type: M3ECarouselType.uncontained,
uncontainedItemExtent: 80,
children: items,
);
// Vertical (hero left/right map to top/bottom)
M3ECarousel(
axis: Axis.vertical,
type: M3ECarouselType.hero,
children: items,
);
M3EListItem
Standard list row with headline, supporting text, and slots.
M3EListItem(
headline: 'Wireless charging',
supportingText: 'On · Fast charge enabled',
leading: const Icon(M3EIcons.schedule),
trailing: const Icon(M3EIcons.chevron_right),
onTap: () {},
);
M3ECardList
Vertically stacked cards with dynamic corner rounding.
M3ECardList(
itemCount: 3,
onTap: (index) {},
itemBuilder: (context, index) => M3EListItem(
headline: 'Inbox',
leading: const Icon(M3EIcons.schedule),
),
);
// Scrollable / lazy
M3ECardList.builder(
itemCount: 20,
shrinkWrap: true,
itemBuilder: (context, index) => M3EListItem(
headline: 'Item $index',
),
);
M3EDismissibleColumn
Vertically swipeable card list with expressive physics.
M3EDismissibleColumn(
itemCount: 3,
onDismiss: (index, direction) async => true,
onTap: (index) {},
itemBuilder: (context, index) => M3EListItem(
headline: 'Swipe to dismiss',
leading: const Icon(M3EIcons.schedule),
),
);
M3EDismissibleList
Horizontal swipeable card list — same API as M3EDismissibleColumn.
SizedBox(
height: 120,
child: M3EDismissibleList(
itemCount: 5,
onDismiss: (index, direction) async => true,
itemBuilder: (context, index) => M3EListItem(
headline: 'Card $index',
),
),
);
M3EExpandableList
Expandable cards with expressive open/close motion.
M3EExpandableList(
data: [
M3EExpandableData(
title: 'Battery level low',
subtitle: 'Plug in your device.',
leading: const Icon(M3EIcons.battery_alert),
body: const Text('Your battery is at 10%.'),
),
],
);
// Scrollable variant for long lists
M3EExpandableList.scrollable(
data: expandableItems,
shrinkWrap: true,
);
// Sliver variant for CustomScrollView
CustomScrollView(
slivers: [
M3EExpandableList.sliver(data: expandableItems),
],
);
M3EDivider
Horizontal and vertical dividers.
const M3EDivider();
Row(
children: [
const Text('Left'),
const SizedBox(width: 12),
const M3EDivider(axis: M3EDividerAxis.vertical),
const SizedBox(width: 12),
const Text('Right'),
],
);
M3EDialog
Modal dialog — use the static .show helper. Optional topDivider /
bottomDivider draw full-bleed lines between header, content, and actions
(padding lives on those sections so dividers can reach the edges).
M3EDialog.show<void>(
context,
dialog: M3EDialog(
title: 'Reset settings?',
content: const Text('This restores default values.'),
topDivider: true,
bottomDivider: true,
actions: [
M3EButton(
style: M3EButtonStyle.text,
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
M3EButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Reset'),
),
],
),
);
// Selection list (single or multi); confirm disabled until a choice is made
final List<String>? picked = await M3EDialog.showSelectionScreen(
context,
title: 'Choose a plan',
options: const <String>['Standard', 'Pro', 'Team'],
multiSelect: false,
);
// Full-screen variant
M3EDialog.showFullScreen<void>(
context,
title: 'New event',
body: const Padding(
padding: EdgeInsets.all(24),
child: Text('Full-screen dialog body.'),
),
);
M3EBottomSheet
Modal bottom sheet — use .show.
M3EBottomSheet.show<void>(
context,
builder: (context) => const Padding(
padding: EdgeInsets.all(24),
child: Text('A modal bottom sheet with a drag handle.'),
),
);
M3ESideSheet
Side sheet panel — use .show.
M3ESideSheet.show<void>(
context,
title: 'Filters',
body: const Padding(
padding: EdgeInsets.all(24),
child: Text('Side sheet content.'),
),
);
Navigation
See also:
example/lib/pages/navigation_page.dart(Nav tab)
M3EAppBar
Top, search, sliver, and bottom app bar variants. Docked top/bottom bars apply
single-edge safeArea padding from MediaQuery.viewPadding by default
(opt out with safeArea: false).
M3EAppBar.top(
titleText: 'Inbox',
leading: const Icon(M3EIcons.menu),
actions: const [Icon(M3EIcons.search)],
);
// Anchored search title — tap opens fullscreen (or docked) search
M3EAppBar.search(
searchController: searchController,
barHintText: 'Search mail',
leading: const Icon(M3EIcons.menu),
actions: const [Icon(M3EIcons.tune)],
suggestionsBuilder: (context, controller) sync* {
yield const ListTile(title: Text('Suggestion'));
},
);
// Sliver (inside CustomScrollView)
M3EAppBar.sliver(
titleText: 'Sliver • medium',
actions: const [Icon(M3EIcons.search)],
);
// Bottom app bar with FAB slot
M3EAppBar.bottom(
actions: const [Icon(M3EIcons.menu), Icon(M3EIcons.search)],
floatingActionButton: M3EFab(
icon: const Icon(M3EIcons.add),
size: M3EFabSize.small,
onPressed: () {},
),
);
M3ETabs
Primary and secondary tab bars.
// in State
M3ETabs(
selectedIndex: tabIndex,
onTabSelected: (i) => setState(() => tabIndex = i),
tabs: const [
M3ETab(label: 'Overview'),
M3ETab(label: 'Specs'),
M3ETab(label: 'Reviews'),
],
);
M3ETabs(
variant: M3ETabsVariant.secondary,
selectedIndex: tabIndex,
onTabSelected: (i) => setState(() => tabIndex = i),
tabs: const [
M3ETab(label: 'Photos', icon: Icon(M3EIcons.calendar_today)),
M3ETab(label: 'Albums', icon: Icon(M3EIcons.menu)),
],
);
M3ENavigationBar
Bottom navigation for compact layouts.
// in State
M3ENavigationBar(
destinations: const [
M3ENavigationBarDestination(icon: Icon(M3EIcons.menu), label: 'Home'),
M3ENavigationBarDestination(
icon: Icon(M3EIcons.search),
label: 'Search',
badgeDot: true,
),
],
selectedIndex: barIndex,
onDestinationSelected: (i) => setState(() => barIndex = i),
);
M3ENavigationRail
Vertical navigation for medium and expanded layouts.
// in State
M3ENavigationRail(
sections: const [
M3ENavigationRailSection(
destinations: [
M3ENavigationRailDestination(
icon: Icon(M3EIcons.menu),
label: 'Home',
),
M3ENavigationRailDestination(
icon: Icon(M3EIcons.search),
label: 'Search',
),
],
),
],
selectedIndex: railIndex,
onDestinationSelected: (i) => setState(() => railIndex = i),
fab: M3ENavigationRailFabSlot(
icon: const Icon(M3EIcons.add),
label: 'Compose',
onPressed: () {},
),
);
M3ENavigationDrawer
Modal navigation drawer.
// in State
M3ENavigationDrawer(
headline: 'Mail',
destinations: const [
M3ENavigationDestination(icon: Icon(M3EIcons.menu), label: 'Home'),
M3ENavigationDestination(
icon: Icon(M3EIcons.search),
label: 'Search',
showBadge: true,
),
],
selectedIndex: drawerIndex,
onDestinationSelected: (i) => setState(() => drawerIndex = i),
);
M3EToolbar
Compose Material 3 expressive floating and docked toolbars. Floating toolbars
own expand/collapse when one action sets isExpandTrigger (expanded is the
initial state; the adjacent FAB stays visible and does not toggle expansion).
Optional visibilityController / scrollBehavior enable scroll-exit or manual
show/hide. Set onActiveIndexChanged for toolbar-managed action selection
(labeled actions animate width). Use fabExpandIcon / fabCollapseIcon when a
FAB morphs with the pill.
// Floating (default) — pill, wrap-content
M3EToolbar(
actions: <M3EToolbarItem>[
M3EToolbarAction(icon: M3EIcons.edit, onPressed: () {}),
M3EToolbarAction(icon: M3EIcons.share, onPressed: () {}),
],
);
// Floating + expand trigger + adjacent FAB
M3EToolbar(
expanded: true,
onExpandedChanged: (open) {},
actions: <M3EToolbarItem>[
M3EToolbarAction(
icon: M3EIcons.menu,
isExpandTrigger: true,
onPressed: () {},
),
M3EToolbarAction(icon: M3EIcons.edit, onPressed: () {}),
M3EToolbarAction(icon: M3EIcons.share, onPressed: () {}),
],
fabIcon: const Icon(M3EIcons.add),
fabExpandIcon: const Icon(M3EIcons.add),
fabCollapseIcon: const Icon(M3EIcons.close),
onFabPressed: () {},
);
// Action selection (internal active index when onActiveIndexChanged is set)
M3EToolbar(
onActiveIndexChanged: (i) {},
actions: <M3EToolbarItem>[
M3EToolbarAction(
icon: M3EIcons.edit,
label: 'Edit',
onPressed: () {},
),
M3EToolbarAction(icon: M3EIcons.share, onPressed: () {}),
],
);
// Scroll-exit / manual visibility
final visibility = M3EToolbarVisibilityController();
M3EToolbarScrollWrapper(
behavior: M3EToolbarScrollBehavior.exitAlways(controller: visibility),
child: ListView(...),
);
M3EToolbar(
visibilityController: visibility,
actions: <M3EToolbarItem>[...],
);
// Mixed icon actions + custom widgets (widgets stay inline; height-capped)
M3EToolbar(
actions: <M3EToolbarItem>[
M3EToolbarAction(icon: M3EIcons.edit, onPressed: () {}),
M3EToolbarWidget(
child: M3ESplitButton<String>(
size: M3EButtonSize.sm,
label: 'Sort',
items: const [
M3ESplitButtonItem(value: 'name', child: 'Name'),
M3ESplitButtonItem(value: 'date', child: 'Date'),
],
onSelected: (_) {},
),
),
M3EToolbarAction(icon: M3EIcons.share, onPressed: () {}),
],
);
// Vertical floating
M3EToolbar(
axis: Axis.vertical,
colorStyle: M3EToolbarColorStyle.vibrant,
actions: <M3EToolbarItem>[...],
);
// Docked — full width; safeArea pads only the dock edge
M3EToolbar.docked(
dockEdge: M3EToolbarDockEdge.bottom,
safeArea: true,
titleText: 'Inbox',
actions: <M3EToolbarItem>[
M3EToolbarAction(icon: M3EIcons.search, onPressed: () {}),
M3EToolbarAction(
icon: M3EIcons.delete,
label: 'Delete',
isDestructive: true,
onPressed: () {},
),
],
);
M3EMenu
Anchored dropdown menu. Top-level M3EMenuGroups each render as an elevated
surface with a gap between them; dividers stay inside a surface.
M3EMenu(
anchorBuilder: (context, open) => M3EButton.icon(
style: M3EButtonStyle.outlined,
icon: const Icon(M3EIcons.arrow_drop_down),
label: const Text('Open menu'),
onPressed: open,
),
children: [
M3EMenuGroup.entries(
entries: [
M3EMenuEntry(
label: 'Edit',
leading: const Icon(M3EIcons.edit),
onPressed: () {},
),
const M3EMenuEntry(label: 'Disabled', enabled: false),
],
),
M3EMenuGroup.entries(
label: 'More',
entries: [
M3EMenuEntry(
label: 'Copy',
trailingText: '⌘C',
onPressed: () {},
),
],
),
],
);
Feedback
See also:
example/lib/pages/feedback_page.dart(Find tab)
M3EBadge
Notification dot or numeric badge on a child.
const M3EBadge(
showDot: true,
child: Icon(M3EIcons.menu, size: 28),
);
const M3EBadge(
count: 8,
child: Icon(M3EIcons.calendar_today, size: 28),
);
M3EProgressIndicator
Material 3 Expressive progress indicators with circular and linear variants,
including Compose-style wavy forms. Null value runs indeterminate animation
(classic linear: dual traveling segments with gaps; wavy linear/circular: m3e
style travel / spin+sweep; classic circular: spinning arc with gaps).
// Classic
const M3EProgressIndicator.circular();
M3EProgressIndicator.circular(value: 0.6);
const M3EProgressIndicator.linear();
SizedBox(
width: 200,
child: M3EProgressIndicator.linear(value: 0.6),
);
// Expressive wavy (Compose CircularWavy / LinearWavy)
const M3EProgressIndicator.circularWavy();
M3EProgressIndicator.circularWavy(value: 0.6);
SizedBox(
width: 200,
child: M3EProgressIndicator.linearWavy(),
);
SizedBox(
width: 200,
child: M3EProgressIndicator.linearWavy(value: 0.6),
);
M3ELoadingIndicator
Expressive loading spinner.
const M3ELoadingIndicator();
const M3ELoadingIndicator(
variant: M3ELoadingIndicatorVariant.contained,
);
M3ERefreshIndicator
Pull-to-refresh wrapper for scrollables.
M3ERefreshIndicator(
onRefresh: () async {
await Future<void>.delayed(const Duration(seconds: 2));
},
child: ListView.builder(
itemCount: 12,
itemBuilder: (context, index) => Text('Item ${index + 1}'),
),
);
// Contained variant
M3ERefreshIndicator.contained(
onRefresh: () async {},
child: listView,
);
M3ETooltip
Descriptive label on hover or long-press.
M3ETooltip(
message: 'Compose a new message',
child: M3EIconButton(
icon: const Icon(M3EIcons.edit),
onPressed: () {},
),
);
M3ESnackbar
Brief feedback message — use .show (hosts via internal M3ESnackbarHost).
M3ESnackbar.show(
context,
message: 'Draft saved',
actionLabel: 'Undo',
onAction: () {},
);
M3ETextField
Filled and outlined text input with floating label.
M3ETextField(
controller: nameController,
label: 'Full name',
supportingText: 'As it appears on your ID',
leading: const Icon(M3EIcons.edit),
);
const M3ETextField(
label: 'Email',
variant: M3ETextFieldVariant.outlined,
errorText: 'Enter a valid email address',
);
M3ESearchBar / M3ESearchAnchor
Inline search field, or a bar that opens a full search view with suggestions.
When embedded in toolbars or app bars, expandOnFocus / expandRestPadding
control the horizontal inset spring on focus.
// Inline bar
M3ESearchBar(
controller: searchController,
hintText: 'Search components',
trailing: [
M3EIconButton(
icon: const Icon(M3EIcons.close),
onPressed: searchController.clear,
),
],
);
// Anchor + search view
final controller = M3ESearchController();
M3ESearchAnchor.bar(
searchController: controller,
barHintText: 'Search',
suggestionsBuilder: (context, controller) sync* {
for (final name in names.where((n) => n.contains(controller.text))) {
yield ListTile(
title: Text(name),
onTap: () => controller.closeView(name),
);
}
},
);
Modal surfaces
Several components present transient UI over the app. They all require a
BuildContext with a Navigator / Overlay ancestor (any MaterialApp or
WidgetsApp provides this):
| Component | API |
|---|---|
M3EDialog |
M3EDialog.show, M3EDialog.showFullScreen |
M3EBottomSheet |
M3EBottomSheet.show |
M3ESideSheet |
M3ESideSheet.show |
M3ESnackbar |
M3ESnackbar.show |
Example app (detailed)
The example/ project is a full gallery app:
- Entry point:
example/lib/main.dart—M3EMaterialAppwithautoTheming,dynamicColoring, and a five-tab gallery shell. - Pages: one file per Material 3 category under
example/lib/pages/. - Theme toggle: app-bar
M3EIconButtoncallsM3ETheme.controllerOf(context)?.toggleBrightness(...).
cd example
flutter pub get
flutter run
Pick a device or simulator when prompted. Use the bottom navigation bar to switch between component groups and the app-bar icon to toggle light/dark mode.
Development
Static analysis and tests:
flutter analyze
flutter test
Optional custom lint rules (if klin_dart is enabled in your environment):
dart run custom_lint
Credits
Several components were ported or vendored from earlier Material 3 Expressive implementations. Thanks to the original authors:
| Author | Components | Source |
|---|---|---|
| Mudit Purohit | Buttons, split buttons, toggle button groups | m3e_buttons |
| Mudit Purohit | Dropdown menus | m3e_dropdown_menu |
| Mudit Purohit | Expandable lists | m3e_expandable |
| Emily | Icon buttons | icon_button_m3e |
| Emily | Navigation bar | navigation_bar_m3e |
| Emily | Navigation rail | navigation_rail_m3e |
| Emily | Loading indicator (Flutter package) | loading_indicator_m3e |
| The Android Open Source Project | Loading indicator (Compose reference) | LoadingIndicator.kt |
| The Android Open Source Project | Slider / RangeSlider / VerticalSlider (Compose reference, material3:1.4.0-alpha01) |
Slider.kt / SliderTokens.kt |
| The Android Open Source Project | Linear / circular wavy progress (Compose reference) | LinearWavyProgressIndicator / CircularWavyProgressIndicator |
| The Android Open Source Project | Floating / docked toolbars (Compose reference, material3:1.4.0-alpha01) |
FloatingToolbar.kt / FlexibleBottomAppBar / DockedToolbarTokens |
| The Flutter Authors | Carousel view layout (CarouselView) |
Flutter SDK / m3_carousel |
| pub.dev | Spring motion (motor), expressive morph polygons (material_new_shapes), dynamic color (dynamic_color) |
See Dependencies |
Copyright notices and licenses from those sources are retained in the
corresponding source files where applicable, and summarized in
NOTICE.
License
Distributed under the MIT License. See LICENSE for details.
Copyright (c) 2026 Paa Developments paa.code.me@gmail.com
Libraries
- components/app_bars/components/m3e_app_bar_semantics
- components/app_bars/enums/m3e_app_bar_enums
- components/app_bars/m3e_app_bars
- components/app_bars/styles/m3e_app_bar_theme
- components/badges/m3e_badges
- components/badges/styles/m3e_badge_theme
- components/bottom_sheets/m3e_bottom_sheets
- components/bottom_sheets/styles/m3e_bottom_sheet_theme
- components/buttons/components/m3e_focus_ring
- components/buttons/components/m3e_no_overflow_strategy
- components/buttons/components/m3e_overflow_strategy
- components/buttons/components/m3e_radius_and_padding_motion
- components/buttons/components/m3e_scroll_overflow_strategy
- components/buttons/styles/m3e_overflow_bottom_sheet_decoration
- components/buttons/styles/m3e_overflow_popup_decoration
- components/cards/enums/m3e_card_variant
- components/cards/m3e_cards
- components/cards/styles/m3e_card_theme
- components/carousel/components/m3e_carousel_view
- components/carousel/components/m3e_carousel_wrapper
- components/carousel/enums/m3e_carousel_type
- components/carousel/m3e_carousel
- components/carousel/styles/m3e_carousel_theme
- components/carousel/utils/m3e_carousel_scroll_helper
- components/checkbox/m3e_checkbox
- components/checkbox/styles/m3e_checkbox_theme
- components/chips/enums/m3e_chip_type
- components/chips/m3e_chips
- components/chips/styles/m3e_chip_theme
- components/date_pickers/components/m3e_calendar_date_range_picker
- components/date_pickers/components/m3e_date_picker_actions
- components/date_pickers/components/m3e_date_picker_dialog_content
- components/date_pickers/components/m3e_date_picker_header
- components/date_pickers/components/m3e_date_picker_mode_toggle
- components/date_pickers/components/m3e_day_cell
- components/date_pickers/components/m3e_day_picker
- components/date_pickers/components/m3e_input_date_picker_form_field
- components/date_pickers/components/m3e_input_date_range_picker_form_field
- components/date_pickers/components/m3e_month_picker
- components/date_pickers/components/m3e_year_picker
- components/date_pickers/enums/m3e_date_picker_enums
- components/date_pickers/m3e_calendar_date_picker
- components/date_pickers/m3e_date_picker_dialog
- components/date_pickers/m3e_date_pickers
- components/date_pickers/m3e_date_range_picker_dialog
- components/date_pickers/models/m3e_calendar_labels
- components/date_pickers/models/m3e_date_picker_models
- components/date_pickers/res/m3e_date_picker_constants
- components/date_pickers/styles/m3e_date_picker_theme
- components/date_pickers/utils/m3e_date_picker_utils
- components/dialogs/m3e_dialogs
- components/dialogs/styles/m3e_dialog_theme
- components/divider/enums/m3e_divider_axis
- components/divider/m3e_divider
- components/divider/styles/m3e_divider_theme
- components/dropdown_menus/components/m3e_dropdown_chips
- components/dropdown_menus/controllers/m3e_dropdown_controller
- components/dropdown_menus/enums/m3e_dropdown_expand_direction
- components/dropdown_menus/models/m3e_dropdown_item
- components/dropdown_menus/styles/m3e_dropdown_chip_style
- components/dropdown_menus/styles/m3e_dropdown_field_style
- components/dropdown_menus/styles/m3e_dropdown_item_style
- components/dropdown_menus/styles/m3e_dropdown_panel_style
- components/dropdown_menus/styles/m3e_dropdown_search_style
- components/dropdown_menus/utils/m3e_dropdown_spring_motion
- components/extended_fabs/m3e_extended_fabs
- components/floating_action_buttons/enums/m3e_fab
- components/floating_action_buttons/styles/m3e_fab_theme
- components/lists/components/m3e_card_list_item
- components/lists/components/m3e_expandable_builders
- components/lists/components/m3e_expandable_data
- components/lists/components/m3e_expandable_item
- components/lists/components/m3e_expandable_list_base
- components/lists/components/m3e_list_item_scope
- components/lists/controllers/m3e_dismissible_card_controller
- components/lists/enums/m3e_expandable_enums
- components/lists/enums/m3e_list_enums
- components/lists/m3e_lists
- components/lists/models/m3e_dismissible_slot
- components/lists/styles/m3e_dismissible_list_style
- components/lists/styles/m3e_expandable_style
- components/lists/styles/m3e_list_theme
- components/lists/utils/m3e_expandable_spring_motion
- components/lists/utils/m3e_measure_size
- components/loading_indicator/components/m3e_expressive_loading_indicator
- components/loading_indicator/enums/m3e_loading_indicator_variant
- components/loading_indicator/m3e_loading_indicator
- components/loading_indicator/styles/m3e_loading_indicator_theme
- components/navigation_rail/components/m3e_rail_badge_view
- components/navigation_rail/components/m3e_rail_item
- components/progress_indicators/components/m3e_circular_progress_painter
- components/progress_indicators/components/m3e_circular_wavy_progress_painter
- components/progress_indicators/components/m3e_linear_progress_painter
- components/progress_indicators/enums/m3e_progress_enums
- components/progress_indicators/m3e_progress_indicators
- components/progress_indicators/styles/m3e_progress_indicator_theme
- components/progress_indicators/utils/m3e_progress_indicator_utils
- components/radio_button/styles/m3e_radio_theme
- components/refresh_indicator/enums/m3e_refresh_status
- components/refresh_indicator/m3e_refresh_indicator
- components/refresh_indicator/styles/m3e_refresh_indicator_theme
- components/search/components/m3e_search_view
- components/search/controllers/m3e_search_controller
- components/search/m3e_search
- components/search/m3e_search_anchor
- components/search/m3e_search_bar
- components/search/res/m3e_search_constants
- components/search/styles/m3e_search_bar_theme
- components/search/styles/m3e_search_view_theme
- components/segmented_buttons/models/m3e_segment
- components/side_sheets/m3e_side_sheets
- components/side_sheets/styles/m3e_side_sheet_theme
- components/sliders/components/m3e_range_slider_track
- components/sliders/components/m3e_slider_centered_track
- components/sliders/components/m3e_slider_dot_overlay
- components/sliders/components/m3e_slider_thumb
- components/sliders/components/m3e_slider_track
- components/sliders/components/m3e_slider_track_painter
- components/sliders/components/m3e_slider_value_indicator
- components/sliders/enums/m3e_slider_enums
- components/sliders/m3e_range_slider
- components/sliders/m3e_sliders
- components/sliders/models/m3e_slider_dot_builder
- components/sliders/models/m3e_slider_range
- components/sliders/models/m3e_slider_range_labels
- components/sliders/models/m3e_slider_track_icons
- components/sliders/res/m3e_slider_tokens
- components/sliders/styles/m3e_slider_theme
- components/sliders/utils/m3e_slider_dot_geometry
- components/sliders/utils/m3e_slider_dot_layout
- components/sliders/utils/m3e_slider_math
- components/sliders/utils/m3e_slider_track_paint_metrics
- components/snackbar/components/m3e_snackbar_host
- components/snackbar/m3e_snackbar
- components/snackbar/styles/m3e_snackbar_theme
- components/switch_control/m3e_switch_control
- components/switch_control/styles/m3e_switch_theme
- components/tabs/enums/m3e_tabs_variant
- components/tabs/m3e_tabs
- components/tabs/models/m3e_tab
- components/tabs/styles/m3e_tab_theme
- components/text_fields/enums/m3e_text_field_variant
- components/text_fields/m3e_text_fields
- components/text_fields/styles/m3e_text_field_theme
- components/time_pickers/components/m3e_dial_time_picker
- components/time_pickers/components/m3e_input_time_picker_form_field
- components/time_pickers/components/m3e_time_dial_painter
- components/time_pickers/components/m3e_time_picker_actions
- components/time_pickers/components/m3e_time_picker_dialog_content
- components/time_pickers/components/m3e_time_picker_header
- components/time_pickers/enums/m3e_time_picker_enums
- components/time_pickers/m3e_time_picker_dialog
- components/time_pickers/m3e_time_pickers
- components/time_pickers/models/m3e_time
- components/time_pickers/res/m3e_time_picker_constants
- components/time_pickers/styles/m3e_time_picker_theme
- components/time_pickers/utils/m3e_time_picker_utils
- components/toolbars/components/m3e_toolbar_actions_row
- components/toolbars/components/m3e_toolbar_body
- components/toolbars/components/m3e_toolbar_expanding_actions
- components/toolbars/components/m3e_toolbar_fab_layout
- components/toolbars/components/m3e_toolbar_fab_slot
- components/toolbars/components/m3e_toolbar_measure_size
- components/toolbars/components/m3e_toolbar_title_block
- components/toolbars/controllers/m3e_toolbar_visibility_controller
- components/toolbars/enums/m3e_toolbar_enums
- components/toolbars/m3e_toolbar_scroll_behavior
- components/toolbars/m3e_toolbars
- components/toolbars/models/m3e_toolbar_item
- components/toolbars/res/m3e_toolbar_tokens
- components/toolbars/styles/m3e_toolbar_theme
- components/toolbars/utils/m3e_toolbar_item_layout
- components/toolbars/utils/m3e_toolbar_spring_motion
- components/tooltips/m3e_tooltips
- components/tooltips/styles/m3e_tooltip_theme
- foundations/components/m3e_component_theme
- foundations/foundations
- Design tokens, theming hosts, and shared interaction primitives for Material 3 Expressive components.
- foundations/m3e_color_scheme
- foundations/m3e_color_utils
- foundations/m3e_dynamic_color_host
- foundations/m3e_elevation
- foundations/m3e_focus
- foundations/m3e_haptics
- foundations/m3e_icons
- foundations/m3e_ink_splash_theme
- foundations/m3e_material_app
- foundations/m3e_material_new_shapes_bridge
- foundations/m3e_motion
- foundations/m3e_resolved_theme
- foundations/m3e_scrim_system_ui
- foundations/m3e_shapes
- foundations/m3e_spacing
- foundations/m3e_state_layer
- foundations/m3e_state_layer_overlay
- foundations/m3e_tappable
- foundations/m3e_tappable_ink_scope
- foundations/m3e_theme
- foundations/m3e_theme_controller
- foundations/m3e_theme_data
- foundations/m3e_theme_defaults
- foundations/m3e_theme_extension
- foundations/m3e_theme_scope
- foundations/m3e_typography
- material_3_expressive
- Material 3 Expressive: a faithful Flutter implementation of the Material 3
component set, exposed as direct
M3E*component widgets.




