material_design 0.6.0
material_design: ^0.6.0 copied to clipboard
The fastest path to consistent Material Design UIs in Flutter. Build beautiful apps aligned with official metrics and guidelines using a powerful set of ready-to-use design tokens and helper widgets.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:material_design/material_design.dart';
import 'package:material_design_example/showcase_pages/foundations/border_tokens_page.dart';
import 'package:material_design_example/showcase_pages/foundations/breakpoint_tokens_page.dart';
import 'package:material_design_example/showcase_pages/foundations/density_page.dart';
import 'package:material_design_example/showcase_pages/foundations/icon_size_tokens_page.dart';
import 'package:material_design_example/showcase_pages/foundations/opacity_tokens_page.dart';
import 'package:material_design_example/showcase_pages/foundations/spacing_page.dart';
import 'package:material_design_example/showcase_pages/foundations/z_index_tokens_page.dart'
show ZIndexTokensPage;
import 'package:material_design_example/showcase_pages/widgets/launch_url_text.dart';
import 'package:provider/provider.dart';
import 'color_picker.dart';
import 'showcase_pages/components/utils_page.dart';
import 'showcase_pages/foundations/accessibility_page.dart';
import 'showcase_pages/foundations/adaptive_page.dart';
import 'showcase_pages/styles/color_page.dart';
import 'showcase_pages/styles/elevation_page.dart';
import 'showcase_pages/styles/motion_page.dart';
import 'showcase_pages/styles/shape_page.dart';
import 'showcase_pages/styles/typography_page.dart';
import 'theme_provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => ThemeProvider(),
child: const TokenShowcaseApp(),
),
);
}
class TokenShowcaseApp extends StatelessWidget {
const TokenShowcaseApp({super.key});
@override
Widget build(BuildContext context) {
final themeProvider = Provider.of<ThemeProvider>(context);
return MaterialApp(
title: 'Material Design 3',
theme: themeProvider.lightTheme,
darkTheme: themeProvider.darkTheme,
themeMode: themeProvider.themeMode,
debugShowCheckedModeBanner: false,
home: const ShowcaseHomePage(),
);
}
}
class ShowcaseHomePage extends StatefulWidget {
const ShowcaseHomePage({super.key});
@override
State<ShowcaseHomePage> createState() => _ShowcaseHomePageState();
}
class _ShowcaseHomePageState extends State<ShowcaseHomePage> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
int _selectedIndex = 0;
final List<Widget> _pages = [
// Foundations
// const DesignTokensPage(),
const SpacingPage(),
const DensityPage(),
const BreakpointTokensPage(),
const ZIndexTokensPage(),
const BorderTokensPage(),
const IconSizeTokensPage(),
const OpacityTokensPage(),
const AccessibilityPage(),
const AdaptivePage(),
// Styles
const ColorPage(),
const TypographyPage(),
const ElevationPage(),
const ShapePage(),
const MotionPage(),
// Components
// const ComponentsShowcasePage(),
const UtilsPage(),
];
@override
Widget build(BuildContext context) {
final isSmallScreen =
MediaQuery.of(context).size.width < M3Breakpoint.medium;
if (isSmallScreen) {
return Scaffold(
key: _scaffoldKey,
drawer: _buildScrollableNavigationDrawer(context),
body: Stack(
children: [
_pages[_selectedIndex],
Positioned(
top: M3Spacing.space8,
left: M3Spacing.space16,
child: IconButton(
tooltip: 'Open navigation menu',
onPressed: () => _scaffoldKey.currentState?.openDrawer(),
icon: const Icon(Icons.menu),
),
),
],
),
);
}
return Scaffold(
body: Row(
children: [
_buildScrollableNavigationRail(context),
const VerticalDivider(thickness: 1, width: 1),
Expanded(child: _pages[_selectedIndex]),
],
),
);
}
List<NavigationRailDestination> _buildFoundationNavigationRailDestinations() {
return const [
NavigationRailDestination(
icon: Icon(Icons.straighten_outlined),
selectedIcon: Icon(Icons.straighten),
label: Text('Spacing'),
),
NavigationRailDestination(
icon: Icon(Icons.density_medium_outlined),
selectedIcon: Icon(Icons.density_medium),
label: Text('Density'),
),
NavigationRailDestination(
icon: Icon(Icons.aspect_ratio_outlined),
selectedIcon: Icon(Icons.aspect_ratio),
label: Text('Breakpoints'),
),
NavigationRailDestination(
icon: Icon(Icons.layers_outlined),
selectedIcon: Icon(Icons.layers),
label: Text('Z-Indexes'),
),
NavigationRailDestination(
icon: Icon(Icons.border_style_outlined),
selectedIcon: Icon(Icons.border_style),
label: Text('Borders'),
),
NavigationRailDestination(
icon: Icon(Icons.photo_size_select_small_outlined),
selectedIcon: Icon(Icons.photo_size_select_small),
label: Text('Icon Sizes'),
),
NavigationRailDestination(
icon: Icon(Icons.opacity_outlined),
selectedIcon: Icon(Icons.opacity),
label: Text('Opacities'),
),
NavigationRailDestination(
icon: Icon(Icons.accessibility_outlined),
selectedIcon: Icon(Icons.accessibility),
label: Text('A11y'),
),
NavigationRailDestination(
icon: Icon(Icons.devices_outlined),
selectedIcon: Icon(Icons.devices),
label: Text('Adaptive'),
),
];
}
List<NavigationRailDestination> _buildStylesNavigationRailDestinations() {
return const [
NavigationRailDestination(
icon: Icon(Icons.format_paint_outlined),
selectedIcon: Icon(Icons.format_paint),
label: Text('Color'),
),
NavigationRailDestination(
icon: Icon(Icons.text_fields_outlined),
selectedIcon: Icon(Icons.text_fields),
label: Text('Typography'),
),
NavigationRailDestination(
icon: Icon(Icons.copy_outlined),
selectedIcon: Icon(Icons.copy),
label: Text('Elevation'),
),
NavigationRailDestination(
icon: Icon(Icons.rounded_corner_outlined),
selectedIcon: Icon(Icons.rounded_corner),
label: Text('Shape'),
),
NavigationRailDestination(
icon: Icon(Icons.animation_outlined),
selectedIcon: Icon(Icons.animation),
label: Text('Motion'),
),
// NavigationRailDestination(
// icon: Icon(Icons.widgets_outlined),
// selectedIcon: Icon(Icons.widgets),
// label: Text('Components'),
// ),
NavigationRailDestination(
icon: Icon(Icons.auto_awesome_outlined),
selectedIcon: Icon(Icons.auto_awesome),
label: Text('Utils'),
),
];
}
Widget _buildScrollableNavigationDrawer(BuildContext context) {
final themeProvider = Provider.of<ThemeProvider>(context);
final textTheme = Theme.of(context).textTheme;
final foundationDestinations = _buildFoundationNavigationRailDestinations();
final styleDestinations = _buildStylesNavigationRailDestinations();
return NavigationDrawer(
selectedIndex: _selectedIndex,
onDestinationSelected: (index) {
setState(() {
_selectedIndex = index;
Navigator.of(context).pop();
});
},
children: [
Padding(
padding: const EdgeInsets.fromLTRB(
M3Spacing.space28,
M3Spacing.space16,
M3Spacing.space16,
M3Spacing.space12,
),
child: Text(
'Material Design 3',
style: textTheme.titleSmall,
),
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: M3Spacing.space28),
child: Divider(),
),
// Foundations Section
Padding(
padding: const EdgeInsets.fromLTRB(
M3Spacing.space28,
M3Spacing.space16,
M3Spacing.space16,
M3Spacing.space12,
),
child: Text('Foundations', style: textTheme.titleSmall),
),
...foundationDestinations.map(
(d) => NavigationDrawerDestination(
icon: d.icon,
label: d.label,
selectedIcon: d.selectedIcon,
),
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: M3Spacing.space28),
child: Divider(),
),
// Styles Section
Padding(
padding: const EdgeInsets.fromLTRB(
M3Spacing.space28,
M3Spacing.space16,
M3Spacing.space16,
M3Spacing.space12,
),
child: Text('Styles', style: textTheme.titleSmall),
),
...styleDestinations.map(
(d) => NavigationDrawerDestination(
icon: d.icon,
label: d.label,
selectedIcon: d.selectedIcon,
),
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: M3Spacing.space28),
child: Divider(),
),
// Theme Controls
Padding(
padding: const EdgeInsets.fromLTRB(
M3Spacing.space28,
M3Spacing.space16,
M3Spacing.space16,
M3Spacing.space12,
),
child: Text('Theme', style: textTheme.titleSmall),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: M3Spacing.space16),
child: Row(
children: [
const Text('Dark Mode'),
const Spacer(),
Switch(
value: themeProvider.themeMode == ThemeMode.dark,
onChanged: (value) {
themeProvider.changeThemeMode(
value ? ThemeMode.dark : ThemeMode.light);
},
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: M3Spacing.space16),
child: Row(
children: [
const Text('Seed Color'),
const Spacer(),
IconButton(
icon: Icon(Icons.color_lens, color: themeProvider.seedColor),
onPressed: () async {
final newColor = await showColorPickerDialog(
context,
themeProvider.seedColor,
);
if (newColor != null) {
themeProvider.changeSeedColor(newColor);
}
},
),
],
),
),
const SizedBox(height: M3Spacing.space8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: LaunchURLText(
label: 'Flutter M3 Demo',
m3Url: 'https://flutterweb-wasm.web.app/',
),
),
],
);
}
Widget _buildScrollableNavigationRail(BuildContext context) {
final themeProvider = Provider.of<ThemeProvider>(context);
final foundationDestinations = _buildFoundationNavigationRailDestinations();
final styleDestinations = _buildStylesNavigationRailDestinations();
return Container(
width: 80,
color: Theme.of(context).colorScheme.surface,
child: Column(
children: [
const SizedBox(height: M3Spacing.space8),
const LaunchURLText(
label: 'M3',
m3Url: 'https://m3.material.io/',
),
const SizedBox(height: M3Spacing.space8),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
const Divider(
indent: M3Spacing.space8,
endIndent: M3Spacing.space8,
),
const LaunchURLText(
label: 'Foundations',
fontSize: 12,
m3Url: 'https://m3.material.io/foundations',
),
...foundationDestinations.asMap().entries.map((entry) {
final index = entry.key;
final destination = entry.value;
return _buildNavigationRailItem(
destination: destination,
index: index,
isSelected: _selectedIndex == index,
onTap: () {
setState(() {
_selectedIndex = index;
});
},
);
}),
const Divider(
indent: M3Spacing.space8,
endIndent: M3Spacing.space8,
),
const LaunchURLText(
label: 'Styles',
fontSize: 12,
m3Url: 'https://m3.material.io/styles',
),
...styleDestinations.asMap().entries.map((entry) {
final index = entry.key + foundationDestinations.length;
final destination = entry.value;
return _buildNavigationRailItem(
destination: destination,
index: index,
isSelected: _selectedIndex == index,
onTap: () {
setState(() {
_selectedIndex = index;
});
},
);
}),
const Divider(
indent: M3Spacing.space8,
endIndent: M3Spacing.space8,
),
const SizedBox(height: M3Spacing.space8),
Switch(
value: themeProvider.themeMode == ThemeMode.dark,
onChanged: (isDark) {
themeProvider.changeThemeMode(
isDark ? ThemeMode.dark : ThemeMode.light,
);
},
),
const Text('Dark Mode', style: TextStyle(fontSize: 12)),
const SizedBox(height: M3Spacing.space16),
IconButton(
icon: Icon(
Icons.color_lens,
color: themeProvider.seedColor,
),
onPressed: () async {
final newColor = await showColorPickerDialog(
context,
themeProvider.seedColor,
);
if (newColor != null) {
themeProvider.changeSeedColor(newColor);
}
},
),
const Text('Seed Color', style: TextStyle(fontSize: 12)),
const SizedBox(height: M3Spacing.space16),
const LaunchURLText(
label: 'Demo',
fontSize: 12,
m3Url: 'https://flutterweb-wasm.web.app/',
),
const SizedBox(height: M3Spacing.space8),
],
),
),
),
],
),
);
}
Widget _buildNavigationRailItem({
required NavigationRailDestination destination,
required int index,
required bool isSelected,
required VoidCallback onTap,
}) {
final colorScheme = Theme.of(context).colorScheme;
return InkWell(
onTap: onTap,
child: Container(
width: 80,
height: 72,
padding: const EdgeInsets.symmetric(vertical: M3Spacing.space4),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 56,
height: 32,
decoration: BoxDecoration(
color: isSelected
? colorScheme.secondaryContainer
: Colors.transparent,
borderRadius: BorderRadius.circular(16),
),
child: Icon(
isSelected
? (destination.selectedIcon as Icon).icon
: (destination.icon as Icon).icon,
color: isSelected
? colorScheme.onSecondaryContainer
: colorScheme.onSurfaceVariant,
size: 24,
),
),
const SizedBox(height: 4),
Text(
(destination.label as Text).data!,
style: TextStyle(
fontSize: 12,
color: isSelected
? colorScheme.onSurface
: colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
}