m3e_buttons 0.0.3
m3e_buttons: ^0.0.3 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: mudit200408.github.io/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.
- 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 #
flutter pub add m3e_buttons
Or add manually to pubspec.yaml:
dependencies:
m3e_buttons: ^0.0.2
import 'package:m3e_buttons/m3e_buttons.dart';
Quick Start #
import 'package:flutter/material.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. |
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. |
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. |
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. |
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 🙏



