M3E Card List

A Flutter package providing expressive, Material 3 card lists with dynamically rounded corners inside normal ListViews and CustomScrollViews (via slivers). It also provides a lightweight M3ECardColumn for static layouts.
It automatically calculates and draws the corners to fit exactly the Material 3 Expressive spec for adjacent items. It gives extensive customization options for developers including customizable splash ripples, custom border colors, custom elevation, and highly tunable haptic feedback.
Features
- Dynamic border radius: The first and last items get a larger outer radius while adjoining cards receive a smaller inner radius seamlessly.
- Sliver Support: Provides
SliverM3ECardListout of the box to beautifully tie into your complexCustomScrollViews andSliverAppBars. - Lightweight Alternative: Use
M3ECardColumnfor static, non-interactive lists. - Individual Card Widget: Use
M3ECarddirectly for full control in custom layouts. - Highly Customizable: Offers parameters for
gap,innerRadius,outerRadius,padding,splashColor,highlightColor, andsplashFactory. - Haptic Feedback: Includes an easy API for
hapticimpact levels using theM3EHapticFeedbackenum (none, light, medium, heavy) on tap. - Accessibility: Built-in
semanticLabel/semanticLabelBuildersupport andonFocusChangecallback. - Hover & Focus: Custom
focusColorandhoverColorfor desktop/mouse interactions. - Empty State:
emptyBuilderparameter for all list components whenitemCountis 0.
🎮 Interactive Demo
You can try out the package demo here: m3e_core demo
Breaking Changes (v0.0.1 → v0.1.0)
1. Haptic API changed from integer to enum
Before (v0.0.1):
M3ECardList(
itemCount: 5,
itemBuilder: (context, index) => Text('Item $index'),
haptic: 3, // 0: None, 1: Light, 2: Medium, 3: Heavy
)
After (v0.1.0):
M3ECardList(
itemCount: 5,
itemBuilder: (context, index) => Text('Item $index'),
haptic: M3EHapticFeedback.heavy, // none, light, medium, heavy
)
2. margin behavior changed (now wraps entire list)
Before: Each individual card applied the margin, causing gaps between cards.
After: The margin now wraps the entire list/group as a single outer padding.
3. margin parameter removed from M3ECard
M3ECard(margin: ...) no longer exists. Use M3ECardList or M3ECardColumn instead for margin.
4. Default padding changed
Before: EdgeInsets.all(16.0)
After: EdgeInsets.all(12.0) — tighter internal spacing in all cards.
5. itemPadding → padding (M3ECardColumn)
Before:
M3ECardColumn(
itemPadding: EdgeInsets.all(12),
children: [...],
)
After:
M3ECardColumn(
padding: EdgeInsets.all(12),
children: [...],
)
7. New M3ECard widget available
You can now use M3ECard directly for individual card rendering with full position control:
M3ECard(
index: 0,
position: M3ECardPosition.first,
outerRadius: 24,
innerRadius: 4,
gap: 3,
child: Text('Individual card'),
onTap: (index) => print('Tapped'),
)
8. M3ECardPosition enum added
Used with M3ECard to specify card position: first, middle, last, single.
9. calculateCardPosition is now public
Previously private logic is now exported in m3e_card_widget.dart:
M3ECardPosition pos = calculateCardPosition(index, itemCount);
Usage
Simply import the package and use M3ECardList, SliverM3ECardList, M3ECardColumn, or M3ECard.
dependencies:
m3e_card_list: ^0.1.0
import 'package:m3e_card_list/m3e_card_list.dart';
M3ECardList(
itemCount: 5,
itemBuilder: (context, index) {
return Text('Data Item $index');
},
onTap: (index) {
print('Tapped $index');
},
// Customize as needed:
haptic: M3EHapticFeedback.medium,
splashColor: Colors.teal.withValues(alpha: 0.2),
highlightColor: Colors.teal.withValues(alpha: 0.1),
);
To entirely remove the ripple effect from a card list, use the splashFactory: NoSplash.splashFactory combined with transparent splash/highlight colors.
M3ECardList(
itemCount: 2,
itemBuilder: (context, index) => Text('No ripple here!'),
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
splashFactory: NoSplash.splashFactory,
onTap: (index) {},
)
Static Column Example
If you don't need the performance overhead of a ListView.builder for small lists, you can use the lightweight M3ECardColumn. It is a simple wrapper for Column that supports all the same InkWell ripples, haptics, and customizations!
M3ECardColumn(
children: [
Text('Static Item 1'),
Text('Static Item 2'),
Text('Static Item 3'),
],
);
Sliver Example
For use inside a CustomScrollView:
CustomScrollView(
slivers: [
SliverM3ECardList(
itemCount: 10,
itemBuilder: (context, index) => Text('Sliver Item $index'),
onTap: (index) {},
),
],
)
Additional information
Check out the example folder for a fully-featured showcase app demonstrating how to use both normal and sliver lists, along with dynamic theming.
API Reference
M3ECard — Individual Card Widget
| Parameter | Type | Default | Description |
|---|---|---|---|
index |
int |
required | Item index (passed to callbacks) |
position |
M3ECardPosition |
required | Card position: first, middle, last, single |
child |
Widget |
required | Primary content of the card |
outerRadius |
double |
required | Radius for outer corners |
innerRadius |
double |
required | Radius for inner corners |
gap |
double |
required | Vertical space below card (ignored for last/single) |
color |
Color? |
surfaceContainerHighest |
Background color |
padding |
EdgeInsetsGeometry? |
EdgeInsets.all(12.0) |
Inner padding around child |
onTap |
void Function(int index)? |
null |
Tap callback |
onLongPress |
void Function(int index)? |
null |
Long-press callback |
semanticLabel |
String? |
null |
Accessibility label for screen readers |
mouseCursor |
MouseCursor? |
auto (click if interactive) |
Mouse cursor on hover |
focusColor |
Color? |
null |
Focus indicator color |
hoverColor |
Color? |
null |
Hover indicator color |
onFocusChange |
void Function(bool)? |
null |
Focus state change callback |
border |
BorderSide? |
BorderSide.none |
Border around the card |
elevation |
double |
0 |
Card elevation |
splashColor |
Color? |
null |
Ink splash color |
highlightColor |
Color? |
null |
Ink highlight color |
splashFactory |
InteractiveInkFeatureFactory? |
null |
Custom splash factory |
enableFeedback |
bool |
true |
Haptic/acoustic feedback on tap |
haptic |
M3EHapticFeedback |
M3EHapticFeedback.none |
Haptic level: none, light, medium, heavy |
Note:
marginis not available onM3ECard. UseM3ECardList.marginorM3ECardColumn.margininstead.
M3ECardList — Standard & Lazy Lists
Common Parameters (both constructors)
| Parameter | Type | Default | Description |
|---|---|---|---|
itemCount |
int |
required | Number of items |
itemBuilder |
IndexedWidgetBuilder |
required | Builds widget for each index |
outerRadius |
double |
24.0 |
Radius for outer corners |
innerRadius |
double |
4.0 |
Radius for inner corners |
gap |
double |
3.0 |
Space between adjacent items |
color |
Color? |
surfaceContainerHighest |
Background color |
padding |
EdgeInsetsGeometry? |
EdgeInsets.all(12.0) |
Inner padding for each card |
margin |
EdgeInsetsGeometry? |
EdgeInsets.zero |
Outer margin around entire list |
onTap |
void Function(int index)? |
null |
Tap callback |
onLongPress |
void Function(int index)? |
null |
Long-press callback |
semanticLabelBuilder |
String Function(int index)? |
null |
Accessibility label builder |
mouseCursor |
MouseCursor? |
auto (click if interactive) |
Mouse cursor on hover |
focusColor |
Color? |
null |
Focus indicator color |
hoverColor |
Color? |
null |
Hover indicator color |
onFocusChange |
void Function(int index, bool)? |
null |
Focus state change callback |
border |
BorderSide? |
BorderSide.none |
Border around each card |
elevation |
double |
0 |
Card elevation |
splashColor |
Color? |
null |
Ink splash color |
highlightColor |
Color? |
null |
Ink highlight color |
splashFactory |
InteractiveInkFeatureFactory? |
null |
Custom splash factory |
enableFeedback |
bool |
true |
Haptic/acoustic feedback on tap |
hptic |
M3EHapticFeedback |
M3EHapticFeedback.none |
Hptic level |
emptyBuilder |
Widget? |
null |
Widget shown when itemCount is 0 |
M3ECardList.builder Additional Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
controller |
ScrollController? |
null |
Scroll position control |
physics |
ScrollPhysics? |
null |
Scroll physics |
shrinkWrap |
bool |
false |
Size to children instead of viewport |
listPadding |
EdgeInsetsGeometry? |
null |
Padding around the scrollable list |
addAutomaticKeepAlives |
bool |
true |
Wrap in AutomaticKeepAlive |
addRepaintBoundaries |
bool |
true |
Wrap in RepaintBoundary |
addSemanticIndexes |
bool |
true |
Wrap in IndexedSemantics |
M3ECardList.of Factory (static children)
| Parameter | Type | Default | Description |
|---|---|---|---|
children |
List<Widget> |
required | Pre-built card contents |
| (all common params inherited) |
M3ECardColumn — Static Column
| Parameter | Type | Default | Description |
|---|---|---|---|
children |
List<Widget> |
required | Pre-built card widgets |
outerRadius |
double |
24.0 |
Radius for outer corners |
innerRadius |
double |
4.0 |
Radius for inner corners |
gap |
double |
3.0 |
Space between adjacent items |
color |
Color? |
surfaceContainerHighest |
Background color |
padding |
EdgeInsetsGeometry? |
EdgeInsets.all(12.0) |
Inner padding for each card |
margin |
EdgeInsetsGeometry? |
EdgeInsets.zero |
Outer margin around entire column |
onTap |
void Function(int index)? |
null |
Tap callback |
onLongPress |
void Function(int index)? |
null |
Long-press callback |
semanticLabelBuilder |
String Function(int index)? |
null |
Accessibility label builder |
mouseCursor |
MouseCursor? |
auto (click if interactive) |
Mouse cursor on hover |
focusColor |
Color? |
null |
Focus indicator color |
hoverColor |
Color? |
null |
Hover indicator color |
onFocusChange |
void Function(int index, bool)? |
null |
Focus state change callback |
border |
BorderSide? |
BorderSide.none |
Border around each card |
elevation |
double |
0 |
Card elevation |
splashColor |
Color? |
null |
Ink splash color |
highlightColor |
Color? |
null |
Ink highlight color |
splashFactory |
InteractiveInkFeatureFactory? |
null |
Custom splash factory |
enableFeedback |
bool |
true |
Haptic/acoustic feedback on tap |
haptic |
M3EHapticFeedback |
M3EHapticFeedback.none |
Haptic level |
emptyBuilder |
Widget? |
null |
Widget shown when children is empty |
SliverM3ECardList — Sliver List
| Parameter | Type | Default | Description |
|---|---|---|---|
itemCount |
int |
required | Number of items |
itemBuilder |
IndexedWidgetBuilder |
required | Builds widget for each index |
outerRadius |
double |
24.0 |
Radius for outer corners |
innerRadius |
double |
4.0 |
Radius for inner corners |
gap |
double |
3.0 |
Space between adjacent items |
color |
Color? |
surfaceContainerHighest |
Background color |
padding |
EdgeInsetsGeometry? |
EdgeInsets.all(12.0) |
Inner padding for each card |
margin |
EdgeInsetsGeometry? |
EdgeInsets.zero |
Outer margin (wraps in SliverPadding) |
onTap |
void Function(int index)? |
null |
Tap callback |
onLongPress |
void Function(int index)? |
null |
Long-press callback |
semanticLabelBuilder |
String Function(int index)? |
null |
Accessibility label builder |
mouseCursor |
MouseCursor? |
auto (click if interactive) |
Mouse cursor on hover |
focusColor |
Color? |
null |
Focus indicator color |
hoverColor |
Color? |
null |
Hover indicator color |
onFocusChange |
void Function(int index, bool)? |
null |
Focus state change callback |
border |
BorderSide? |
BorderSide.none |
Border around each card |
elevation |
double |
0 |
Card elevation |
splashColor |
Color? |
null |
Ink splash color |
highlightColor |
Color? |
null |
Ink highlight color |
splashFactory |
InteractiveInkFeatureFactory? |
null |
Custom splash factory |
enableFeedback |
bool |
true |
Haptic/acoustic feedback on tap |
haptic |
M3EHapticFeedback |
M3EHapticFeedback.none |
Haptic level |
emptyBuilder |
Widget? |
null |
Widget shown when itemCount is 0 |
addAutomaticKeepAlives |
bool |
true |
Wrap in AutomaticKeepAlive |
addRepaintBoundaries |
bool |
true |
Wrap in RepaintBoundary |
addSemanticIndexes |
bool |
true |
Wrap in IndexedSemantics |
Helper Functions & Enums
| Name | Type | Description |
|---|---|---|
M3EHapticFeedback |
enum |
Haptic levels: none, light, medium, heavy |
M3ECardPosition |
enum |
Card positions: first, middle, last, single |
calculateCardPosition(index, total) |
M3ECardPosition Function(int, int) |
Calculates position from index and total |
applyHaptic(M3EHapticFeedback) |
void Function(...) |
Triggers haptic feedback |