A highly customisable side navigation menu for Flutter. Supports Material 3 theming, glassmorphism, floating card panels, expansion items, badges, and responsive auto-collapse — with zero external dependencies.
Screenshots
Auto
Live demo
https://jamalianpour.github.io/easy_sidemenu
Getting started
Add the dependency:
dependencies:
easy_sidemenu: ^1.0.0
import 'package:easy_sidemenu/easy_sidemenu.dart' ;
Basic usage
import 'package:easy_sidemenu/easy_sidemenu.dart' ;
import 'package:flutter/material.dart' ;
class MyHomePage extends StatefulWidget {
const MyHomePage ({super .key});
@override
State <MyHomePage > createState () => _MyHomePageState ();
}
class _MyHomePageState extends State <MyHomePage > {
final _controller = SideMenuController ();
final _pageController = PageController ();
@override
void initState () {
super .initState ();
_controller.addListener (() {
_pageController.jumpToPage (_controller.currentIndex);
});
}
@override
void dispose () {
_controller.dispose ();
_pageController.dispose ();
super .dispose ();
}
@override
Widget build (BuildContext context) {
return Scaffold (
body: Row (
children: [
SideMenu (
controller: _controller,
items: [
SideMenuItem (
title: 'Dashboard' ,
icon: const Icon (Icons .dashboard_rounded),
onTap: (index, controller) => controller.goTo (index),
),
SideMenuItem (
title: 'Settings' ,
icon: const Icon (Icons .settings_rounded),
onTap: (index, controller) => controller.goTo (index),
),
],
),
Expanded (
child: PageView (
controller: _pageController,
physics: const NeverScrollableScrollPhysics (),
children: const [
Center (child: Text ('Dashboard' )),
Center (child: Text ('Settings' )),
],
),
),
],
),
);
}
}
Items
SideMenuItem
SideMenuItem (
title: 'Dashboard' ,
icon: const Icon (Icons .dashboard_rounded),
badge: const Text ('3' , style: TextStyle (color: Colors .white, fontSize: 10 )),
tooltipContent: 'Dashboard' ,
trailing: const Icon (Icons .chevron_right, size: 16 ),
onTap: (index, controller) => controller.goTo (index),
),
Custom builder
Replace the default layout entirely for a single item:
SideMenuItem (
builder: (context, displayMode) {
return const Divider (height: 24 , indent: 8 , endIndent: 8 );
},
),
SideMenuExpansionItem
SideMenuExpansionItem (
title: 'Projects' ,
icon: const Icon (Icons .folder_rounded),
initiallyExpanded: false ,
children: [
SideMenuItem (
title: 'Active' ,
icon: const Icon (Icons .circle, size: 10 ),
onTap: (index, controller) => controller.goTo (index),
),
SideMenuItem (
title: 'Archived' ,
icon: const Icon (Icons .circle_outlined, size: 10 ),
onTap: (index, controller) => controller.goTo (index),
),
],
),
Controller
SideMenuController extends ChangeNotifier:
final controller = SideMenuController (initialIndex: 0 );
controller.goTo (2 );
print (controller.currentIndex);
controller.addListener (() {
print ('Selected: ${controller.currentIndex} ' );
});
controller.dispose ();
Theming
SideMenuThemeData is a Material 3 ThemeExtension. Register it once for the whole app, or pass it directly to a single SideMenu.
App-wide (recommended)
MaterialApp (
theme: ThemeData (
colorScheme: ColorScheme .fromSeed (seedColor: Colors .blue),
useMaterial3: true ,
extensions: const [
SideMenuThemeData (
openWidth: 260 ,
selectedColor: Color (0xFF2563EB ),
selectedIconColor: Colors .white,
selectedTitleStyle: TextStyle (color: Colors .white, fontWeight: FontWeight .w600),
),
],
),
)
Per-widget override
SideMenu (
controller: controller,
theme: const SideMenuThemeData (
displayMode: SideMenuDisplayMode .compact,
compactWidth: 72 ,
),
items: [...],
)
SideMenuThemeData properties
Property
Type
Default
Description
displayMode
SideMenuDisplayMode
auto
auto / open / compact
openWidth
double
300
Width in open mode
compactWidth
double
70
Width in compact mode
collapseWidth
double
600
Screen width at which auto switches to compact
menuDecoration
BoxDecoration?
null
Full decoration for the menu container (overrides backgroundColor)
backdropSigma
double?
null
Blur sigma for glassmorphism — wraps the menu in BackdropFilter
backgroundColor
Color?
ColorScheme.surface
Menu background (ignored when menuDecoration is set)
selectedItemDecoration
BoxDecoration?
null
Full decoration for the selected item (overrides selectedColor)
selectedColor
Color?
ColorScheme.primaryContainer
Selected item highlight
hoverColor
Color?
onSurface 8%
Hover highlight
selectedHoverColor
Color?
primaryContainer 80%
Hover colour when item is also selected
selectedIconColor
Color?
ColorScheme.onPrimaryContainer
Icon colour when selected
unselectedIconColor
Color?
ColorScheme.onSurfaceVariant
Icon colour when unselected
iconSize
double
24
Icon size
selectedTitleStyle
TextStyle?
labelLarge / onPrimaryContainer
Title text style when selected
unselectedTitleStyle
TextStyle?
labelLarge / onSurfaceVariant
Title text style when unselected
itemHeight
double
50
Item row height
itemBorderRadius
BorderRadius
circular(8)
Item corner radius
itemOuterPadding
EdgeInsetsGeometry
symmetric(h: 5)
Padding around each item
itemInnerSpacing
double
8
Spacing between icon and title
showTooltip
bool
true
Show item title as tooltip in compact mode
showHamburger
bool
false
Show a hamburger button that collapses the menu
toggleColor
Color?
onSurfaceVariant
Expand/collapse toggle icon colour
expansionArrowColor
Color?
onSurfaceVariant
Expansion arrow in collapsed state
expansionArrowOpenColor
Color?
ColorScheme.primary
Expansion arrow in open state
SideMenu properties
Property
Type
Default
Description
controller
SideMenuController
required
Manages the selected index
items
List<SideMenuItemBase>
required
Items to display
theme
SideMenuThemeData?
null
Per-widget theme override
title
Widget?
null
Widget shown above items
footer
Widget?
null
Widget pinned to the bottom
alwaysShowFooter
bool
false
Keep footer visible in compact mode
showToggle
bool
false
Show collapse/expand toggle button
collapseWidth
double
600
Screen width threshold for auto mode
displayModeToggleDuration
Duration
350 ms
Open ↔ compact animation duration
onDisplayModeChanged
ValueChanged<SideMenuDisplayMode>?
null
Called when display mode changes
Advanced customisation
Gradient sidebar
SideMenu (
controller: controller,
theme: const SideMenuThemeData (
menuDecoration: BoxDecoration (
gradient: LinearGradient (
colors: [Color (0xFF667EEA ), Color (0xFF764BA2 )],
begin: Alignment .topCenter,
end: Alignment .bottomCenter,
),
),
selectedColor: Color (0x33FFFFFF ),
selectedIconColor: Colors .white,
),
items: [...],
)
Left-border selected indicator (Minimal style)
SideMenu (
controller: controller,
theme: const SideMenuThemeData (
selectedItemDecoration: BoxDecoration (
color: Color (0xFFEFF6FF ),
border: Border (left: BorderSide (color: Color (0xFF2563EB ), width: 3 )),
),
itemBorderRadius: BorderRadius .zero,
itemOuterPadding: EdgeInsets .zero,
),
items: [...],
)
Glassmorphism
Requires a backdropSigma and a semi-transparent menuDecoration. Place the SideMenu inside a widget that has a visible background (image, gradient) behind it.
Container (
decoration: const BoxDecoration (
gradient: LinearGradient (
colors: [Color (0xFF1A1A2E ), Color (0xFF0F3460 )],
),
),
child: Row (
children: [
SideMenu (
controller: controller,
theme: SideMenuThemeData (
backdropSigma: 18 ,
menuDecoration: BoxDecoration (
color: Colors .white.withValues (alpha: 0.15 ),
border: Border (
right: BorderSide (color: Colors .white.withValues (alpha: 0.25 )),
),
),
selectedIconColor: Colors .white,
unselectedIconColor: Colors .white70,
),
items: [...],
),
Expanded (child: ...),
],
),
)
Floating card (rounded + shadow)
Wrap the SideMenu in a Padding and set menuDecoration with a borderRadius and boxShadow:
Row (
children: [
Padding (
padding: const EdgeInsets .all (8 ),
child: SideMenu (
controller: controller,
theme: SideMenuThemeData (
menuDecoration: BoxDecoration (
color: Colors .white,
borderRadius: const BorderRadius .all (Radius .circular (16 )),
border: Border .all (color: const Color (0xFFE2E8F0 )),
boxShadow: [
BoxShadow (
color: Colors .black.withValues (alpha: 0.08 ),
blurRadius: 24 ,
offset: const Offset (4 , 0 ),
),
],
),
),
items: [...],
),
),
Expanded (child: ...),
],
)
Migrating from v0.7
See MIGRATION.md for a complete before/after guide covering every breaking change.
Quick summary:
v0.7
v1.0
SideMenuStyle(...)
SideMenuThemeData(...)
style: param on SideMenu
theme: param on SideMenu
openSideMenuWidth
openWidth
compactSideMenuWidth
compactWidth
controller.changePage(i)
controller.goTo(i)
controller.currentPage
controller.currentIndex
controller.addListener((i) {...})
controller.addListener(() { use controller.currentIndex })
SideMenuItemType
SideMenuItemBase
badgeContent:
badge:
initialExpanded:
initiallyExpanded:
Contributing
Issues and pull requests are welcome on GitHub .
License
MIT — see LICENSE for details.