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
Actions — buttons, FABs, and button groups Selection — segmented controls, chips, menus, and sliders
Containment Navigation
Containment — pickers, cards, lists, and dialogs Navigation — app bars, tabs, nav bar, and toolbars
Feedback
Feedback — progress, badges, text fields, and snackbars

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, and proper state layers on every interactive surface.
  • Design token foundations — color schemes, typography, motion, shapes (including material_new_shapes morph polygons), elevation, and state layers via the M3ETheme inherited 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.3

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';

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:

  • colorSchemeM3EColorScheme with M3 semantic roles
  • typeScaleM3ETypeScale (display, headline, title, label, body)
  • spacing, visualDensity, per-component *Theme extensions

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.

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.

M3EFabMenu(
  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.

// in State
M3ESwitch(
  value: wifiEnabled,
  selectedIcon: const Icon(M3EIcons.check),
  onChanged: (v) => setState(() => wifiEnabled = 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, thumbLength, dotSize, dotSpacing, and dotBuilder customize track, thumb, and stop/tick markers.

// 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: 20,
  thumbLength: 36,
  dotSize: 12,
  dotSpacing: 8,
  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.'),
  ),
);

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).

// 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),
  onFabPressed: () {},
);

// 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.

// 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(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),
      );
    }
  },
);

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.dartM3EMaterialApp with autoTheming, dynamicColoring, and a five-tab gallery shell.
  • Pages: one file per Material 3 category under example/lib/pages/.
  • Theme toggle: app-bar M3EIconButton calls M3ETheme.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_base_button_state
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/enums/m3e_button_enums
components/buttons/m3e_buttons
components/buttons/models/m3e_button_measurements
components/buttons/res/m3e_button_constants
components/buttons/styles/m3e_button_decoration
components/buttons/styles/m3e_button_motion
components/buttons/styles/m3e_button_theme
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/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/components/m3e_dropdown_menu_item
components/dropdown_menus/controllers/m3e_dropdown_controller
components/dropdown_menus/enums/m3e_dropdown_expand_direction
components/dropdown_menus/m3e_dropdown_menus
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_menu_theme
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/fab_menu/m3e_fab_menu
components/fab_menu/models/m3e_fab_menu_item
components/fab_menu/styles/m3e_fab_menu_theme
components/floating_action_buttons/enums/m3e_fab
components/floating_action_buttons/m3e_floating_action_buttons
components/floating_action_buttons/styles/m3e_fab_theme
components/icon_buttons/enums/m3e_icon_button_enums
components/icon_buttons/m3e_icon_buttons
components/icon_buttons/styles/m3e_icon_button_shapes
components/icon_buttons/styles/m3e_icon_button_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/menus/components/m3e_menu_content
components/menus/components/m3e_menu_divider
components/menus/components/m3e_menu_item
components/menus/components/m3e_menu_node_builders
components/menus/components/m3e_menu_popup
components/menus/components/m3e_menu_style_scope
components/menus/enums/m3e_menu_anchor_position
components/menus/enums/m3e_menu_color_style
components/menus/enums/m3e_menu_item_shape
components/menus/m3e_menus
components/menus/models/m3e_menu_entry
components/menus/models/m3e_menu_node
components/menus/styles/m3e_menu_theme
components/menus/utils/m3e_menu_placer
components/menus/utils/m3e_menu_spring_motion
components/navigation_bar/components/m3e_nav_badge_view
components/navigation_bar/components/m3e_nav_bar_destination_button
components/navigation_bar/enums/m3e_nav_bar_enums
components/navigation_bar/m3e_navigation_bar
components/navigation_bar/models/m3e_nav_metrics
components/navigation_bar/models/m3e_navigation_bar_destination
components/navigation_bar/styles/m3e_navigation_bar_theme
components/navigation_drawer/components/m3e_drawer_destination_button
components/navigation_drawer/m3e_navigation_drawer
components/navigation_drawer/models/m3e_navigation_destination
components/navigation_drawer/styles/m3e_navigation_drawer_theme
components/navigation_rail/components/m3e_nav_icon_scale
components/navigation_rail/components/m3e_nav_selection_indicator
components/navigation_rail/components/m3e_rail_badge_view
components/navigation_rail/components/m3e_rail_item
components/navigation_rail/components/m3e_rail_item_button
components/navigation_rail/enums/m3e_navigation_rail_enums
components/navigation_rail/m3e_navigation_rail
components/navigation_rail/models/m3e_navigation_rail_destination
components/navigation_rail/models/m3e_navigation_rail_fab_slot
components/navigation_rail/models/m3e_navigation_rail_section
components/navigation_rail/res/m3e_navigation_rail_layout
components/navigation_rail/styles/m3e_navigation_rail_theme
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/radio_button/m3e_radio_button
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_anchor
components/search/res/m3e_search_constants
components/search/styles/m3e_search_bar_theme
components/search/styles/m3e_search_view_theme
components/segmented_buttons/m3e_segmented_buttons
components/segmented_buttons/models/m3e_segment
components/segmented_buttons/styles/m3e_segmented_button_theme
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/split_buttons/components/m3e_split_button_bottom_sheet
components/split_buttons/enums/m3e_split_button_menu_style
components/split_buttons/enums/m3e_split_button_selection_mode
components/split_buttons/enums/m3e_split_button_trailing_alignment
components/split_buttons/m3e_split_buttons
components/split_buttons/models/m3e_split_button_item
components/split_buttons/styles/m3e_split_button_bottom_sheet_decoration
components/split_buttons/styles/m3e_split_button_checkbox_style
components/split_buttons/styles/m3e_split_button_decoration
components/split_buttons/styles/m3e_split_button_popup_decoration
components/split_buttons/styles/m3e_split_button_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/toggle_button/m3e_toggle_button
components/toggle_button/styles/m3e_toggle_button_theme
components/toggle_button_group/components/m3e_toggle_button_group_item_scope
components/toggle_button_group/components/m3e_toggle_button_group_provider
components/toggle_button_group/components/m3e_toggle_button_group_scope
components/toggle_button_group/controllers/m3e_button_group_overflow_controller
components/toggle_button_group/enums/m3e_toggle_button_group_enums
components/toggle_button_group/m3e_toggle_button_group
components/toggle_button_group/models/m3e_button_group_action
components/toggle_button_group/models/m3e_button_group_overflow_paging_window
components/toggle_button_group/styles/m3e_toggle_button_group_theme
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_slot
components/toolbars/components/m3e_toolbar_icon_button
components/toolbars/components/m3e_toolbar_overflow_menu
components/toolbars/components/m3e_toolbar_title_block
components/toolbars/enums/m3e_toolbar_enums
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/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_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.