m3e_buttons

Flutter Pub Version License Material 3 Expressive

M3E Buttons

Buttons the way they were meant to feel. Spring physics. Shape morphing. Five styles, five sizes, zero compromises.

Inspired by the Jetpack Compose implementation of Material 3 Expressive, m3e_buttons brings spring-driven shape animations, a rich customization model, and tactile responsiveness to Flutter — without writing a single line of animation code yourself.

Live example: m3e_core

Jetpack Compose Inspiration

Warning

Breaking changes in v0.0.3+ — If you are upgrading from v0.0.1, please read the Migration Guide before updating. Several parameters have been renamed, removed, or replaced with WidgetStateProperty-based equivalents. Component class names have also changed.


Table of Contents


Why m3e_buttons?

Flutter's built-in buttons don't animate their shape on press. Material 3 Expressive changes that with spring-physics-based radius squish on press, expansion on hover, and shape morphing for toggle state. m3e_buttons implements all of this faithfully in pure Flutter.

  • Localized rebuilds — Interaction-driven animations use ValueNotifier patterns so only the affected widget rebuilds on hover or press.
  • Spring animations — Radius, padding, and focus rings all use motor-backed spring physics.
  • Five stylesfilled, tonal, elevated, outlined, text.
  • Five size presetsxs, sm, md, lg, xl — plus M3EButtonSize.custom(...).
  • Toggle buttons — Icon/label swap animations with expressive checked-state shape morphing.
  • Connected toggle groups — Neighbor-squish, single- and multi-select, overflow handling.
  • Split buttons — Dual-segment with popup, bottom sheet, or custom menu.
  • FABs and FAB menus — Token-sized FABs, extended FABs, and staggered expanding action menus.
  • Decoration-based styling — One M3EButtonDecoration controls everything. No ButtonStyle juggling.
  • Haptic feedback — Four levels, baked into the decoration model.

Screenshots

Button Split Button Toggle Button Group
M3E Button Split Button Connected Group
Hovered Focused Focused Toggle
Hovered Focused Focused Toggle
Split Button Menu Hovered Split Toggle Overflow
Split Menu Hovered Split Overflow

Installation

Important

Flutter 3.47+ & material_ui Requirement (v1.0.0+): Starting with v1.0.0, m3e_buttons is migrated to use the standalone material_ui package decoupled in Flutter 3.47.0.

  • Requires Flutter SDK >=3.47.0.
  • Ensure your app imports package:material_ui/material_ui.dart (or run dart fix --apply --code=migrate_design_widgets).
  • If you are on Flutter < 3.47.0, please use m3e_buttons: ^0.0.6.

Add m3e_buttons and material_ui to your pubspec.yaml:

dependencies:
  material_ui: ^1.0.0
  m3e_buttons: ^1.0.1
import 'package:material_ui/material_ui.dart';
import 'package:m3e_buttons/m3e_buttons.dart';

Quick Start

import 'package:material_ui/material_ui.dart';
import 'package:m3e_buttons/m3e_buttons.dart';

class Demo extends StatelessWidget {
  const Demo({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        M3EFilledButton(
          onPressed: () {},
          child: const Text('Save'),
        ),
        const SizedBox(height: 12),
        M3EElevatedToggleButton(
          icon: const Icon(Icons.favorite_border),
          checkedIcon: const Icon(Icons.favorite),
          onCheckedChange: (checked) {},
        ),
        const SizedBox(height: 12),
        M3EFilledSplitButton<String>(
          label: 'Actions',
          leadingIcon: Icons.more_horiz,
          items: const [
            M3ESplitButtonItem(value: 'edit', child: Text('Edit')),
            M3ESplitButtonItem(value: 'share', child: Text('Share')),
          ],
          onSelected: (value) {},
          onPressed: () {},
        ),
      ],
    );
  }
}

Migration from v0.0.1 to v0.0.3

Caution

This release contains breaking API changes. Review each section below before upgrading.

1. WidgetStateProperty shift

Most color and cursor parameters have transitioned from flat types to WidgetStateProperty. Use the .styleFrom() helper to pass flat values — it handles the mapping automatically.

// v0.0.1
M3EButtonDecoration(backgroundColor: Colors.blue)

// v0.0.3
M3EButtonDecoration.styleFrom(backgroundColor: Colors.blue)

2. Removed parameters

Parameter Affected Classes Migration
size All decoration classes Move size to the button widget itself.
disabledBackgroundColor All decorations Use backgroundColor with WidgetStateProperty.
disabledForegroundColor All decorations Use foregroundColor with WidgetStateProperty.
connectedHoveredInnerRadius M3EToggleButtonDecoration Logic merged into hoveredRadius.
connectedPressedInnerRadius M3EToggleButtonDecoration Logic merged into pressedRadius.

3, double? borderRadius

// v0.0.1
M3EButtonDecoration(borderRadius: BorderRadius.circular(24))

// v0.0.3
M3EButtonDecoration.styleFrom(bborderRadius: 24)

3. Renamed components

v0.0.1 v0.0.3
SplitButtonM3E M3ESplitButton
SplitButtonM3EItem M3ESplitButtonItem
SplitButtonM3EDecoration M3ESplitButtonDecoration

Drop-in Flutter Replacement

For standard action buttons, m3e_buttons aligns closely with Flutter's built-in API. Prefix existing class names with M3E and you're done — no logic changes required.

Flutter M3E Button M3E Toggle Button M3E Split Button
FilledButton M3EFilledButton M3EFilledToggleButton M3EFilledSplitButton
FilledButton.tonal M3EFilledButton.tonal M3EFilledToggleButton.tonal M3EFilledSplitButton.tonal
ElevatedButton M3EElevatedButton M3EElevatedToggleButton M3EElevatedSplitButton
OutlinedButton M3EOutlinedButton M3EOutlinedToggleButton M3EOutlinedSplitButton
TextButton M3ETextButton M3ETextToggleButton (text not supported)
// Before
ElevatedButton(
  onPressed: () {},
  child: const Text('Click Me'),
)

// After
M3EElevatedButton(
  onPressed: () {},
  child: const Text('Click Me'),
)

Components

M3EButton

The standard single-action button. Spring-animated radius squish on press, expansion on hover, and a focus ring that tracks the animated shape.

M3EButton
M3EButton(
  style: M3EButtonStyle.filled,
  size: M3EButtonSize.md,
  shape: M3EButtonShape.round,
  onPressed: () {},
  child: const Row(
    mainAxisSize: MainAxisSize.min,
    children: [
      Icon(Icons.send),
      SizedBox(width: 8),
      Text('Send'),
    ],
  ),
)

Tip: Use M3EButton.icon(...) for the standard icon + label layout without manual Row wrapping. For common styles, prefer the specialized classes — M3EFilledButton, M3EElevatedButton, M3EOutlinedButton, M3ETextButton — for a cleaner API.

Specialized Subclasses

All four style variants are available as dedicated classes — identical API, just with style pre-wired:

// Instead of:
M3EButton(style: M3EButtonStyle.elevated, onPressed: () {}, child: const Text('Upload'))

// Use:
M3EElevatedButton(onPressed: () {}, child: const Text('Upload'))

Parameters

Parameter Type Default Description
onPressed VoidCallback? required Tap callback. null disables the button.
child Widget? null Primary content.
style M3EButtonStyle filled Visual style.
size M3EButtonSize sm Size preset. Overridden by decoration.fixedSize.
shape M3EButtonShape round Corner radius strategy. Ignored when decoration.borderRadius is set.
enabled bool true Disables without removing from the tree.
decoration M3EButtonDecoration? null Colors, radius, motion, haptics.
focusNode FocusNode? null External focus node.
autofocus bool false Focus on mount.
onFocusChange ValueChanged<bool>? null Focus change callback.
semanticLabel String? null Accessibility label.
tooltip String? null Hover hint shown after delay.
mouseCursor MouseCursor SystemMouseCursors.click Cursor on hover.
onLongPress VoidCallback? null Long-press callback.
onHover ValueChanged<bool>? null Hover change callback.
enableFeedback bool true Ripple and native haptic.
splashFactory InteractiveInkFeatureFactory? InkRipple.splashFactory Custom splash factory.
statesController WidgetStatesController? null Programmatic state control.

M3EToggleButton

A stateful toggle with shape morphing between unchecked (round) and checked (square) states. Supports icon-only, icon + label, or label-only content with smooth animated label transitions.

M3EToggleButton
M3EToggleButton(
  icon: const Icon(Icons.bookmark_border),
  checkedIcon: const Icon(Icons.bookmark),
  label: const Text('Save'),
  checkedLabel: const Text('Saved'),
  decoration: const M3EToggleButtonDecoration(
    haptic: M3EHapticFeedback.light,
    motion: M3EMotion.expressiveSpatialDefault,
  ),
  onCheckedChange: (checked) {},
)

Specialized Subclasses

All five style variants are available as dedicated classes — identical API to M3EToggleButton, with style pre-wired:

Class Style
M3EFilledToggleButton M3EButtonStyle.filled
M3EFilledToggleButton.tonal M3EButtonStyle.tonal
M3EElevatedToggleButton M3EButtonStyle.elevated
M3EOutlinedToggleButton M3EButtonStyle.outlined
M3ETextToggleButton M3EButtonStyle.text
// Instead of:
M3EToggleButton(style: M3EButtonStyle.outlined, onCheckedChange: (v) {}, icon: const Icon(Icons.star_border))

// Use:
M3EOutlinedToggleButton(onCheckedChange: (v) {}, icon: const Icon(Icons.star_border))

Parameters

Parameter Type Default Description
onCheckedChange ValueChanged<bool> required Called on toggle.
icon Widget? null Unchecked icon.
checkedIcon Widget? null Checked icon. Falls back to icon.
label Widget? null Text label (makes button content-width).
checkedLabel Widget? null Checked label. Falls back to label.
allowMultilineLabel bool false When true, enables wrapping multiline label content instead of clipping.
checked bool? null External state. null = internal management.
style M3EButtonStyle filled Visual style.
size M3EButtonSize sm Size preset.
enabled bool true Enables or disables the toggle.
decoration M3EToggleButtonDecoration? null Full decoration bundle.
focusNode FocusNode? null External focus node.
autofocus bool false Focus on mount.
semanticLabel String? null Accessibility label.
tooltip String? null Hover hint shown after delay.

M3EToggleButtonGroup

A horizontal or vertical row of M3EToggleButtons with neighbor-squish animation — pressing a button expands it while compressing its neighbors. Supports single-select, multi-select, connected layout, and overflow handling.

Connected Group Overflow
// Single-select connected group
M3EToggleButtonGroup(
  type: M3EButtonGroupType.connected,
  selectedIndex: _selected,
  onSelectedIndexChanged: (index) => setState(() => _selected = index),
  actions: const [
    M3EToggleButtonGroupAction(icon: Icon(Icons.format_bold), semanticLabel: 'Bold'),
    M3EToggleButtonGroupAction(icon: Icon(Icons.format_italic), semanticLabel: 'Italic'),
    M3EToggleButtonGroupAction(icon: Icon(Icons.format_underline), semanticLabel: 'Underline'),
  ],
)

// Multi-select with labels
M3EToggleButtonGroup(
  selectedIndices: _selectedIndices,
  onSelectedIndicesChanged: (indices) => setState(() => _selectedIndices = indices),
  size: M3EButtonSize.md,
  actions: [
    M3EToggleButtonGroupAction(icon: const Icon(Icons.music_note), label: const Text('Music')),
    M3EToggleButtonGroupAction(icon: const Icon(Icons.movie), label: const Text('Movies')),
    M3EToggleButtonGroupAction(icon: const Icon(Icons.book), label: const Text('Books')),
  ],
)

Parameters

Parameter Type Default Description
actions List<M3EToggleButtonGroupAction> required Buttons in the group.
type M3EButtonGroupType standard standard (gaps) or connected (shared edges).
shape M3EButtonShape round Corner radius strategy. Ignored when decoration.borderRadius is set.
size M3EButtonSize sm Size preset.
style M3EButtonStyle filled Visual style.
density M3EButtonGroupDensity regular regular or compact.
spacing double? null Custom gap in dp. Overrides density. Ignored when type is connected.
direction Axis horizontal Layout direction.
selectedIndex int? null Controlled single-select state.
selectedIndices Set<int>? null Controlled multi-select state.
onSelectedIndexChanged ValueChanged<int?>? null Single-select callback. null = deselected.
onSelectedIndicesChanged ValueChanged<Set<int>>? null Multi-select callback.
neighborSquish bool true Neighbor compression on press.
expandedRatio double 0.15 Press expansion factor relative to natural width.
haptic M3EHapticFeedback none Group-level haptics.
decoration M3EToggleButtonDecoration? null Group-level decoration. Per-action decoration takes precedence.
semanticLabel String? null Accessibility label for the group container.
overflow M3EButtonGroupOverflow scroll Overflow behavior.
overflowMenuStyle M3EButtonGroupOverflowMenuStyle popup popup or bottomSheet.

M3EToggleButtonGroupAction

Parameter Type Default Description
icon Widget? null Unchecked icon. At least icon or label is required.
checkedIcon Widget? null Checked icon. Falls back to icon.
label Widget? null Text label.
checkedLabel Widget? null Checked label. Falls back to label.
checked bool? null Per-action controlled state. Do not combine with group-level selectedIndex.
enabled bool true Enables or disables this action.
decoration M3EToggleButtonDecoration? null Per-button decoration override.
width double? null Fixed width overriding natural content width.
focusNode FocusNode? null External focus node.
semanticLabel String? null Accessibility label.
tooltip String? null Hover hint shown after delay.

M3ESplitButton<T>

A dual-segment button — primary action on the left, chevron-triggered dropdown on the right. The trailing segment morphs to a circle when the menu is open on md, lg, and xl sizes.

Split Button Split Button Menu
M3ESplitButton<String>(
  label: 'Sort',
  leadingIcon: Icons.sort,
  style: M3EButtonStyle.filled,
  size: M3EButtonSize.md,
  items: const [
    M3ESplitButtonItem(value: 'name', child: Text('By Name')),
    M3ESplitButtonItem(value: 'date', child: Text('By Date')),
    M3ESplitButtonItem(value: 'size', child: Text('By Size')),
  ],
  onSelected: (value) {},
  onPressed: () {},
  leadingTooltip: 'Sort',
  trailingTooltip: 'More sort options',
)

Either items or menuBuilder must be provided.

Specialized Subclasses

Four style variants are available as dedicated classes — identical API to M3ESplitButton, with style pre-wired. M3ETextSplitButton does not exist because text style is not supported by split buttons.

Class Style
M3EFilledSplitButton M3EButtonStyle.filled
M3EFilledSplitButton.tonal M3EButtonStyle.tonal
M3EElevatedSplitButton M3EButtonStyle.elevated
M3EOutlinedSplitButton M3EButtonStyle.outlined
// Instead of:
M3ESplitButton<String>(style: M3EButtonStyle.elevated, items: [...], onSelected: (v) {}, onPressed: () {})

// Use:
M3EElevatedSplitButton<String>(items: [...], onSelected: (v) {}, onPressed: () {})

Parameters

Parameter Type Default Description
items List<M3ESplitButtonItem<T>>? Dropdown menu items.
menuBuilder List<PopupMenuEntry<T>> Function(BuildContext)? null Custom menu builder. Overrides items.
onSelected ValueChanged<T>? null Menu item selection callback.
onPressed VoidCallback? null Primary segment tap callback.
label String? null Text on the leading segment.
leadingIcon IconData? null Leading icon on the primary segment.
size M3EButtonSize sm Size preset. Overridden by decoration.fixedSize.
shape M3EButtonShape round Corner radius strategy.
style M3EButtonStyle filled Visual style. text is not supported.
trailingAlignment M3ESplitButtonTrailingAlignment opticalCenter opticalCenter or geometricCenter.
leadingTooltip String? null Tooltip for the leading segment.
trailingTooltip String? null Tooltip for the trailing segment.
enabled bool true Disables both segments.
decoration M3ESplitButtonDecoration? null Full decoration bundle.
selectedValue T? null Currently selected value for menu state display.

FABs

m3e_buttons includes two FAB API families:

  • M3EFab, M3EExtendedFab, and M3EFabMenu are the Material 3 Expressive token-first APIs with FAB color roles, FAB sizes, press scale, and menu motion.
  • M3EFloatingActionButton and M3EExtendedFloatingActionButton are button-decoration-based APIs for projects that want FAB sizing while reusing M3EButtonDecoration.

M3EFab

M3EFab(
  icon: const Icon(Icons.add),
  onPressed: () {},
  size: M3EFabSize.medium,
  color: M3EFabColor.primary,
  tooltip: 'Create',
)

Use M3EFab when you want the M3E FAB token model directly. It applies token dimensions, icon sizing, color roles, rounded container radii, and spring press scaling.

Parameter Type Default Description
icon Widget required Icon content shown in the FAB.
onPressed VoidCallback? null Tap callback. null disables the FAB.
size M3EFabSize medium FAB token size: small, medium, or large.
color M3EFabColor primary Color role: primary, secondary, tertiary, or surface.
cornerRadius double? null Overrides the size token radius.
decoration M3EFabDecoration? null Overrides colors, border, overlay, press scale, and press motion.
tooltip String? null Tooltip and accessibility hint.
focusNode FocusNode? null External focus node.
autofocus bool false Requests focus when inserted into the tree.

M3EExtendedFab

M3EExtendedFab(
  icon: const Icon(Icons.edit),
  label: 'Compose',
  onPressed: () {},
  size: M3EFabSize.medium,
  color: M3EFabColor.secondary,
  extended: true,
)

M3EExtendedFab uses the same color and decoration model as M3EFab, but adds a text label and animated extended/collapsed layout.

Parameter Type Default Description
label String required Text label shown when extended.
icon Widget required Leading icon content.
onPressed VoidCallback? null Tap callback. null disables the FAB.
size M3EFabSize medium FAB token size.
color M3EFabColor primary Color role.
extended bool true Shows the full label when true; collapses to icon-only when false.
decoration M3EFabDecoration? null Overrides colors, border, overlay, press scale, and press motion.
focusNode FocusNode? null External focus node.
autofocus bool false Requests focus when inserted into the tree.

M3EFabMenu

M3EFabMenu(
  position: M3EFabMenuPosition.right,
  color: M3EFabColor.primary,
  size: M3EFabSize.medium,
  onOpenChanged: (open) {},
  items: [
    M3EFabMenuItem(
      icon: const Icon(Icons.share),
      label: 'Share',
      onPressed: () {},
    ),
    M3EFabMenuItem(
      icon: const Icon(Icons.favorite),
      label: 'Favorite',
      backgroundColor: Colors.pink.shade100,
      foregroundColor: Colors.pink.shade900,
      onPressed: () {},
    ),
  ],
)

M3EFabMenu renders a trigger FAB and opens a staggered vertical list of action pills in an overlay. The trigger icon morphs between expand and collapse states, while each menu item animates with spring motion.

Parameter Type Default Description
items List<M3EFabMenuItem> required Menu actions shown when open.
icon Widget Icon(Icons.add_rounded) Closed trigger icon.
closeIcon Widget Icon(Icons.close_rounded) Open trigger icon.
expandIcon Widget? null Optional override for the closed trigger icon. Falls back to icon.
collapseIcon Widget? null Optional override for the open trigger icon. Falls back to closeIcon.
color M3EFabColor primary Trigger FAB color role.
size M3EFabSize medium Trigger FAB token size.
position M3EFabMenuPosition right Horizontal alignment for action pills.
decoration M3EFabMenuDecoration? null Menu spacing, item sizing, colors, scrim, and motion.
onOpenChanged ValueChanged<bool>? null Called whenever the menu opens or closes.

M3EFabMenuItem

Parameter Type Default Description
icon Widget required Icon shown in the action pill.
label String required Text shown next to the icon.
onPressed VoidCallback? null Action callback. The menu closes after this callback runs.
backgroundColor Color? null Per-item background override.
foregroundColor Color? null Per-item icon and label color override.

M3EFloatingActionButton

M3EFloatingActionButton(
  onPressed: () {},
  child: const Icon(Icons.add),
  size: M3EFloatingActionButtonSize.standard,
  containerColor: Colors.deepPurple,
  contentColor: Colors.white,
)

This API wraps M3EButton with FAB dimensions and uses M3EButtonDecoration. Choose it when you prefer generic button styles and decoration composition over FAB-specific color roles.

Parameter Type Default Description
onPressed VoidCallback? required Tap callback. null disables the button.
child Widget required FAB content.
size M3EFloatingActionButtonSize standard Size token: small, standard, medium, or large.
containerColor Color? colorScheme.primaryContainer Background override.
contentColor Color? colorScheme.onPrimaryContainer Foreground override.
elevation double? 6 Elevation override.
shape M3EButtonShape round Uses circular radius when round, token radius otherwise.
decoration M3EButtonDecoration? null Full M3EButtonDecoration override bundle.
enabled bool true Disables without removing from the tree.
tooltip String? null Tooltip text.

Specialized constructors are available as subclasses with the size pre-wired:

Class Size
M3ESmallFloatingActionButton M3EFloatingActionButtonSize.small
M3EMediumFloatingActionButton M3EFloatingActionButtonSize.medium
M3ELargeFloatingActionButton M3EFloatingActionButtonSize.large

M3EExtendedFloatingActionButton

M3EExtendedFloatingActionButton(
  onPressed: () {},
  icon: const Icon(Icons.navigation),
  label: const Text('Navigate'),
  expanded: true,
  motion: M3EMotion.standardSpatialFast,
  effectsMotion: M3EMotion.standardEffectsFast,
)

This API extends the decoration-based FAB family with animated width and label opacity.

Parameter Type Default Description
onPressed VoidCallback? required Tap callback. null disables the button.
icon Widget required Leading icon.
label Widget required Label shown when expanded.
expanded bool true Animates between icon-only and extended layout.
size M3EFloatingActionButtonSize standard Size token.
containerColor Color? colorScheme.primaryContainer Background override.
contentColor Color? colorScheme.onPrimaryContainer Foreground override.
elevation double? 6 Elevation override.
decoration M3EButtonDecoration? null Full M3EButtonDecoration override bundle.
enabled bool true Disables without removing from the tree.
tooltip String? null Tooltip text.
motion M3EMotion standardSpatialFast Width animation spring.
effectsMotion M3EMotion standardEffectsFast Label opacity and effect spring.

Specialized subclasses are available with the size pre-wired:

Class Size
M3ESmallExtendedFloatingActionButton M3EFloatingActionButtonSize.small
M3EMediumExtendedFloatingActionButton M3EFloatingActionButtonSize.medium
M3ELargeExtendedFloatingActionButton M3EFloatingActionButtonSize.large

FAB Enums

Enum Values Description
M3EFabColor primary, secondary, tertiary, surface M3E FAB color role resolved from ColorScheme.
M3EFabSize small, medium, large M3E token-first FAB sizes used by M3EFab, M3EExtendedFab, and M3EFabMenu.
M3EFabMenuPosition left, right Menu item horizontal anchor.
M3EFloatingActionButtonSize small, standard, medium, large Decoration-based FAB size tokens.

M3EFabDecoration

M3EFab(
  icon: const Icon(Icons.add),
  onPressed: () {},
  decoration: M3EFabDecoration(
    backgroundColor: WidgetStatePropertyAll(Colors.deepPurple),
    foregroundColor: WidgetStatePropertyAll(Colors.white),
    pressedScale: 0.92,
    motion: M3EMotion.custom(380, 0.55),
  ),
)
Field Type Description
backgroundColor WidgetStateProperty<Color?>? FAB background override.
foregroundColor WidgetStateProperty<Color?>? Icon and label color override.
overlayColor WidgetStateProperty<Color?>? Pressed, hovered, and focused overlay override.
side WidgetStateProperty<BorderSide?>? Border side override.
pressedScale double? Scale target while pressed. Defaults to the component token.
motion M3EMotion? Spring motion used for press scaling.

M3EFabMenuDecoration

M3EFabMenu(
  items: const [],
  decoration: M3EFabMenuDecoration(
    menuOffset: 16,
    itemGap: 10,
    itemHeight: 56,
    itemHorizontalPadding: 20,
    iconSize: 24,
    iconLabelGap: 12,
    itemElevation: 6,
    closedFabSize: 56,
    openFabSize: 64,
    scrimColor: Color(0x33000000),
    expandMotion: M3EMotion.standardPopup,
    fabShapeMotion: M3EMotion.standardPopup,
    expandStaggerMs: 35,
  ),
)
Field Type Description
menuOffset double Vertical gap between trigger FAB and nearest menu item.
itemGap double Vertical gap between action pills.
itemHeight double Action pill height.
itemHorizontalPadding double Horizontal padding inside each action pill.
iconSize double Icon size inside each action pill.
iconLabelGap double Gap between item icon and label.
itemElevation double Elevation for each action pill.
closedFabSize double? Trigger size while closed. Defaults to the FAB size token.
openFabSize double? Trigger size while open. Defaults to closedFabSize or the FAB size token.
itemBackgroundColor Color? Default background color for action pills.
itemForegroundColor Color? Default icon and label color for action pills.
scrimColor Color Fullscreen overlay color behind the open menu. Defaults transparent.
expandMotion M3EMotion Spring used by action pill expansion.
fabShapeMotion M3EMotion Spring used by trigger shape morphing.
expandStaggerMs int Delay between each action pill animation.

Decoration System

All decoration classes are @immutable and support copyWith. Pass null for any field to use token defaults. Use .styleFrom() on any decoration class to pass flat values — WidgetStateProperty mapping is handled automatically. borderRadius is the highest-priority base shape override in decorations and takes precedence over widget shape.

M3EButtonDecoration

M3EButton(
  decoration: M3EButtonDecoration.styleFrom(
    backgroundColor: Colors.deepPurple,
    foregroundColor: Colors.white,
    motion: M3EMotion.expressiveSpatialFast,
    haptic: M3EHapticFeedback.medium,
    borderRadius: 18.0,
    pressedRadius: 8.0,
    hoveredRadius: 20.0,
  ),
  onPressed: () {},
  child: const Text('Custom'),
)
Field Type Description
backgroundColor WidgetStateProperty<Color?>? Button fill.
foregroundColor WidgetStateProperty<Color?>? Text and icon color.
shadowColor WidgetStateProperty<Color?>? Shadow color.
elevation WidgetStateProperty<double?>? Shadow depth.
side WidgetStateProperty<BorderSide?>? Custom border.
padding EdgeInsetsGeometry? Internal content padding.
minimumSize Size? Minimum dimensions. Overrides token size.
fixedSize Size? Fixed dimensions. Overrides token size.
maximumSize Size? Maximum dimensions. Overrides token size.
textStyle TextStyle? Label text style.
iconSize double? Leading icon size.
motion M3EMotion? Spring physics preset.
haptic M3EHapticFeedback? Haptic feedback level.
borderRadius double? Base corner radius. Overrides widget shape.
hoveredRadius double? Corner radius on hover.
pressedRadius double? Corner radius during press squish.
overlayColor WidgetStateProperty<Color?>? Custom pressed/hovered overlay.
backgroundBuilder ButtonLayerBuilder? Custom background layer (clips to radius).
foregroundBuilder ButtonLayerBuilder? Custom foreground layer (clips to radius).

M3EToggleButtonDecoration

M3EToggleButton(
  decoration: M3EToggleButtonDecoration.styleFrom(
    backgroundColor: Colors.grey.shade200,
    foregroundColor: Colors.grey.shade700,
    checkedBackgroundColor: Colors.indigo,
    checkedForegroundColor: Colors.white,
    borderRadius: 18.0,
    checkedRadius: 8.0,
    uncheckedRadius: 24.0,
    haptic: M3EHapticFeedback.light,
  ),
  icon: const Icon(Icons.star_border),
  checkedIcon: const Icon(Icons.star),
  onCheckedChange: (v) {},
)
Field Type Description
backgroundColor WidgetStateProperty<Color?>? Background color.
foregroundColor WidgetStateProperty<Color?>? Text/icon color.
side WidgetStateProperty<BorderSide?>? Custom border.
motion M3EMotion? Spring physics preset.
haptic M3EHapticFeedback? Haptic feedback level.
borderRadius double? Base corner radius used before state-specific radius overrides.
checkedRadius double? Corner radius in checked state.
uncheckedRadius double? Corner radius in unchecked state.
pressedRadius double? Corner radius during press.
hoveredRadius double? Corner radius on hover.
connectedInnerRadius double? Inner corner radius for connected groups.
alignment AlignmentGeometry? Custom alignment for button content.

M3ESplitButtonDecoration

M3ESplitButton<String>(
  decoration: M3ESplitButtonDecoration.styleFrom(
    backgroundColor: Colors.teal,
    foregroundColor: Colors.white,
    borderRadius: 18.0,
    trailingBackgroundColor: Colors.teal.shade700,
    menuBackgroundColor: Colors.teal.shade800,
    menuForegroundColor: Colors.white,
    haptic: M3EHapticFeedback.light,
  ),
  items: const [...],
  onSelected: (v) {},
  onPressed: () {},
)
Field Type Description
backgroundColor WidgetStateProperty<Color?>? Base background.
foregroundColor WidgetStateProperty<Color?>? Base foreground.
borderRadius double? Base corner radius. Overrides widget shape.
trailingBackgroundColor Color? Dropdown segment background.
trailingForegroundColor Color? Dropdown segment icon color.
menuBackgroundColor Color? Menu background.
menuForegroundColor Color? Menu text/icon color.
dividerColor Color? Divider between segments.
gap double? Gap between segments.
menuStyle SplitButtonMenuStyle popup, bottomSheet, or native.
trailingSelectedRadius double? Corner radius when menu is open.

Enums & Tokens

M3EButtonStyle

Value Description
filled Solid primary-container background. Highest prominence.
tonal Secondary-container tinted background.
elevated Surface color with drop shadow.
outlined Transparent with visible border.
text No background or border. Lowest prominence.

M3EButtonSize

Preset Height Use case
xs 32 dp Compact, inline contexts.
sm 40 dp Standard default.
md 56 dp Prominent actions.
lg 96 dp Hero-level actions.
xl 136 dp Full-bleed, expressive.
custom(...) Arbitrary Override any dimension.
M3EButtonSize.custom(
  height: 48,
  hPadding: 20,
  iconSize: 20,
  iconGap: 8,
  width: 200,
)

M3EButtonShape

Value Description
round Pill shape (height / 2 radius).
square Token-defined radius for the current size.

M3EHapticFeedback

Value Description
none No haptic (default).
light Subtle toggle feedback.
medium Standard press feel.
heavy Significant action confirmation.

M3EButtonGroupType

Value Description
standard Buttons with gaps.
connected Shared edges with animated inner corners.

M3EButtonGroupOverflow

Value Description
none No overflow handling.
scroll Scrollable along the main axis.
menu Overflow items in a popup or bottom sheet.
experimentalPaging In-place window paging (experimental).

Motion System

M3EMotion configures spring physics for all animations. Use a preset or define a custom spring.

M3EButtonDecoration(
  motion: M3EMotion.expressiveSpatialDefault,
)

// Custom spring
M3EButtonDecoration(
  motion: M3EMotion.custom(800, 0.65),
)
Preset Stiffness Damping Best for
standardSpatialFast 1400 0.9 Snappy shape transitions
standardSpatialDefault 700 0.9 Balanced shape animation
standardSpatialSlow 300 0.9 Dramatic shape animation
expressiveSpatialFast 800 0.6 Bouncy, responsive
expressiveSpatialDefault 380 0.8 ✅ Default toggle motion
expressiveSpatialSlow 200 0.8 Highly bouncy, dramatic
standardEffectsFast 3800 1.0 Instant effect snap
standardEffectsDefault 1600 1.0 Balanced effects
standardEffectsSlow 800 1.0 Relaxed effects
standardOverflow 1600 0.85 Overflow menu spring
standardPopup 1000 0.6 Popup menu bounce

M3EMotion.custom(double stiffness, double damping) higher stiffness = faster/snappier; damping at 1.0 = critically damped (no overshoot), 0.1-0.5 = very bouncy, 0.5–0.8 = less bouncy.


Accessibility

  • Provide semanticLabel when icon or short text is ambiguous on its own.
  • Maintain sufficient contrast between enabled and disabled states.
  • Validate keyboard navigation on desktop/web — all buttons support FocusNode and autofocus.
  • M3EToggleButton automatically exposes checked semantics to the accessibility tree.
  • M3EToggleButtonGroup wraps its content in a semantic container labelled by semanticLabel.

Architecture

lib/
├── m3e_buttons.dart              ← Public API entry point
└── src/
    ├── components/
    │   ├── m3e_button/           ← M3EButton
    │   ├── m3e_toggle_button/    ← M3EToggleButton + M3EToggleButtonGroup
    │   └── m3e_split_button/     ← M3ESplitButton
    ├── style/
    │   ├── m3e_button_enums.dart
    │   ├── m3e_button_group_enums.dart
    │   ├── m3e_motion.dart
    │   ├── m3e_button_decoration.dart
    │   ├── m3e_split_button_decoration.dart
    │   ├── m3e_button_overflow_decoration.dart
    │   └── button_tokens_adapter.dart
    ├── core/
    └── internal/

Only symbols re-exported from package:m3e_buttons/m3e_buttons.dart are stable public API.


Development

flutter analyze
flutter test

cd example && flutter pub get && flutter run

Contributing

Found a bug or have a feature request? Open an issue or submit a pull request.


💡 Want more Material 3 Expressive components? Check out M3E Core — the all-in-one package.


Radhe Radhe 🙏

Libraries

m3e_buttons