m3e_buttons 1.0.1
m3e_buttons: ^1.0.1 copied to clipboard
Material 3 Expressive button components for Flutter with spring motion, toggle groups, and split menus.
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
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?
- Screenshots
- Installation
- Quick Start
- Migration from v0.0.1 to v0.0.3
- Drop-in Flutter Replacement
- Components
- Decoration System
- Enums & Tokens
- Motion System
- Accessibility
- Architecture
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
ValueNotifierpatterns so only the affected widget rebuilds on hover or press. - Spring animations — Radius, padding, and focus rings all use
motor-backed spring physics. - Five styles —
filled,tonal,elevated,outlined,text. - Five size presets —
xs,sm,md,lg,xl— plusM3EButtonSize.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
M3EButtonDecorationcontrols everything. NoButtonStylejuggling. - Haptic feedback — Four levels, baked into the decoration model.
Screenshots #
| Button | Split Button | Toggle Button Group |
|---|---|---|
![]() |
![]() |
![]() |
| Hovered | Focused | Focused Toggle |
|---|---|---|
![]() |
![]() |
![]() |
| Split Button Menu | Hovered Split | Toggle 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 rundart fix --apply --code=migrate_design_widgets). - If you are on Flutter
< 3.47.0, please usem3e_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(
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 manualRowwrapping. 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(
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.
// 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.
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
itemsormenuBuildermust 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, andM3EFabMenuare the Material 3 Expressive token-first APIs with FAB color roles, FAB sizes, press scale, and menu motion.M3EFloatingActionButtonandM3EExtendedFloatingActionButtonare button-decoration-based APIs for projects that want FAB sizing while reusingM3EButtonDecoration.
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
semanticLabelwhen 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
FocusNodeandautofocus. M3EToggleButtonautomatically exposescheckedsemantics to the accessibility tree.M3EToggleButtonGroupwraps its content in a semantic container labelled bysemanticLabel.
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 🙏



