Responsive Flutter Best Practices
Responsive design in Flutter means creating interfaces that automatically adjust to the available screen space, window size, orientation, and input method. A responsive Flutter application should provide a comfortable experience on phones, tablets, foldables, desktops, and web browsers.
Flutter distinguishes between responsive design, which focuses on fitting UI into available space, and adaptive design, which focuses on making the UI usable in that space. In practice, modern Flutter applications often need both. :contentReference[oaicite:0]{index=0}
1. Why Responsive Design Is Important in Flutter
A Flutter application can run on many screen sizes and platforms. A layout that looks good on a mobile phone may become too stretched, crowded, or difficult to use on a tablet or desktop.
- Provides a better user experience across devices.
- Prevents overflow and clipped content.
- Improves usability on tablets and desktop screens.
- Supports resizable application windows.
- Works better with foldables and multi-window environments.
- Supports different input methods such as touch, mouse, trackpad, and keyboard.
- Makes applications easier to maintain because the same codebase can support multiple layouts.
2. Responsive vs Adaptive Design
| Responsive Design | Adaptive Design |
|---|
| Fits UI into available space. | Chooses an appropriate UI structure for the available space. |
| Changes sizes, spacing, columns, and positions. | May change navigation or information architecture. |
| Example: changing a 4-column grid to 2 columns. | Example: changing bottom navigation to NavigationRail. |
| Focuses mainly on layout. | Focuses on usability and interaction. |
Flutter recommends considering both responsive and adaptive behavior when building applications for multiple form factors. :contentReference[oaicite:1]{index=1}
3. Best Practice: Design for Available Space
Do not assume that a particular device type always has a particular amount of space. A tablet can run an application in split-screen mode, a desktop application can be resized, and a phone may display an application in a constrained window.
Therefore, layout decisions should generally be based on the available window size rather than assumptions such as "phone", "tablet", or "desktop". :contentReference[oaicite:2]{index=2}
Example
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
return const MobileLayout();
}
return const LargeScreenLayout();
},
)
4. Use MediaQuery for Application Window Information
MediaQuery provides information about the current application window, including its size and accessibility-related settings.
Example
final size = MediaQuery.sizeOf(context);
print('Width: ${size.width}');
print('Height: ${size.height}');
Responsive Width Example
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
return Scaffold(
body: Center(
child: Text(
width < 600
? 'Small Screen'
: 'Large Screen',
),
),
);
}
For modern responsive layouts, Flutter documentation recommends using the available window size rather than relying on physical device assumptions. :contentReference[oaicite:3]{index=3}
5. Use LayoutBuilder for Local Responsive Layouts
LayoutBuilder is useful when a widget needs to respond to the space provided by its parent rather than the entire application window.
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 400) {
return const Text('Compact Layout');
}
return const Text('Wide Layout');
},
)
MediaQuery vs LayoutBuilder
| MediaQuery | LayoutBuilder |
|---|
| Works with application window information. | Works with parent constraints. |
| Useful for page-level decisions. | Useful for component-level decisions. |
| Returns size information. | Provides BoxConstraints. |
| Good for navigation/layout changes. | Good for reusable responsive widgets. |
Flutter's adaptive design guidance recommends MediaQuery.sizeOf for window-level measurements and LayoutBuilder when the layout depends on the constraints available to a specific widget. :contentReference[oaicite:4]{index=4}
6. Use Meaningful Breakpoints
A breakpoint is a width at which the application changes its layout.
const double mobileBreakpoint = 600;
const double tabletBreakpoint = 900;
const double desktopBreakpoint = 1200;
These values are examples, not universal device definitions. Choose breakpoints according to where your actual layout needs to change. Flutter documentation emphasizes using available window size rather than device type. :contentReference[oaicite:5]{index=5}
Three-Level Layout
Widget buildResponsiveLayout(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
if (width < 600) {
return const MobileLayout();
} else if (width < 1200) {
return const TabletLayout();
} else {
return const DesktopLayout();
}
}
7. Do Not Check Whether the Device Is a Phone or Tablet
A common mistake is writing layout logic based on device categories.
Bad Approach
if (isTablet) {
return TabletLayout();
} else {
return MobileLayout();
}
Better Approach
final width = MediaQuery.sizeOf(context).width;
if (width >= 600) {
return const LargeLayout();
}
return const SmallLayout();
The available application window can differ from the physical screen because of resizing, split-screen mode, desktop windows, ChromeOS, and other environments. :contentReference[oaicite:6]{index=6}
8. Avoid Orientation-Only Layout Decisions
Do not assume that portrait always means mobile and landscape always means tablet or desktop. A wide desktop window and a landscape phone may have very different available widths.
Less Reliable Approach
if (MediaQuery.orientationOf(context) == Orientation.landscape) {
return const DesktopLayout();
}
Better Approach
final width = MediaQuery.sizeOf(context).width;
if (width >= 900) {
return const WideLayout();
}
return const CompactLayout();
Flutter recommends using available size with MediaQuery.sizeOf or LayoutBuilder instead of using orientation alone for major layout decisions. :contentReference[oaicite:7]{index=7}
9. Avoid Fixed Widths
Hard-coded widths can easily create overflow on smaller screens.
Problematic Example
Container(
width: 700,
child: const Text('Responsive content'),
)
Better Example
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
child: const Text('Responsive content'),
)
For large screens, however, using every available pixel can make text fields and content unnecessarily wide. A maximum width is often better.
10. Use ConstrainedBox for Large Screens
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 900,
),
child: const Padding(
padding: EdgeInsets.all(24),
child: Text(
'This content remains readable on large screens.',
),
),
),
)
This technique prevents content from becoming excessively wide on desktop or web screens. Flutter's large-screen guidance specifically recommends avoiding layouts where text or boxes simply stretch across the entire available width. :contentReference[oaicite:8]{index=8}
11. Use SafeArea
SafeArea helps prevent content from being hidden behind system UI, camera cutouts, notches, rounded display edges, and status bars.
Scaffold(
body: SafeArea(
child: Column(
children: [
const Text('Dashboard'),
Expanded(
child: ListView(
children: const [
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
],
),
),
],
),
),
)
Wrapping the main body of a Scaffold with SafeArea is a useful starting point for many applications, although the correct placement depends on the design. :contentReference[oaicite:9]{index=9}
12. Build Mobile-First Interfaces
Start with a simple layout that works well in limited space, then enhance it for larger windows.
Mobile
Column(
children: [
const ProductImage(),
const ProductDetails(),
const BuyButton(),
],
)
Large Screen
Row(
children: [
Expanded(
child: ProductImage(),
),
Expanded(
child: Column(
children: const [
ProductDetails(),
BuyButton(),
],
),
),
],
)
This approach helps ensure that the most important functionality remains available even when screen space is limited.
13. Use Row and Column Responsively
One common responsive pattern is switching between vertical and horizontal layouts.
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
return const Column(
children: [
ProfileImage(),
ProfileDetails(),
],
);
}
return const Row(
children: [
Expanded(child: ProfileImage()),
Expanded(child: ProfileDetails()),
],
);
},
)
14. Use Expanded and Flexible
Expanded and Flexible help widgets share available space without relying on fixed widths.
Row(
children: [
Expanded(
flex: 2,
child: Container(
height: 100,
child: const Center(child: Text('Main')),
),
),
Expanded(
flex: 1,
child: Container(
height: 100,
child: const Center(child: Text('Side')),
),
),
],
)
The flex values determine how the available space is distributed.
15. Make Grids Responsive
A grid should generally change based on available width instead of using a hard-coded number of columns for a specific device.
GridView.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 280,
mainAxisSpacing: 16,
crossAxisSpacing: 16,
childAspectRatio: 1.2,
),
itemCount: products.length,
itemBuilder: (context, index) {
return ProductCard(product: products[index]);
},
)
A maximum item width allows the number of columns to adjust naturally as the available width changes. Flutter's large-screen guidance recommends this type of approach for responsive grids. :contentReference[oaicite:10]{index=10}
16. Responsive Navigation
Navigation is one of the most important parts of adaptive design. A compact window may work better with bottom navigation, while a wider window can use a navigation rail.
Widget buildNavigation(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
if (width < 600) {
return const NavigationBar(
destinations: [
NavigationDestination(
icon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.person),
label: 'Profile',
),
],
);
}
return const NavigationRail(
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.person),
label: Text('Profile'),
),
],
selectedIndex: 0,
);
}
The exact breakpoint should be based on the space required by your design. Flutter's adaptive documentation gives 600 logical pixels as an example for switching between some compact and larger navigation patterns. :contentReference[oaicite:11]{index=11}
17. Responsive Sidebar Layout
Large screens can show navigation and content side by side.
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 700) {
return const MobileHomePage();
}
return Row(
children: [
SizedBox(
width: 260,
child: Sidebar(),
),
const VerticalDivider(width: 1),
const Expanded(
child: DetailsPage(),
),
],
);
},
)
This sidebar-and-details pattern is commonly useful for tablet and desktop layouts. :contentReference[oaicite:12]{index=12}
18. Use Wrap for Responsive Buttons and Chips
Wrap is useful when items should move to another line when horizontal space becomes insufficient.
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton(
onPressed: () {},
child: const Text('All'),
),
ElevatedButton(
onPressed: () {},
child: const Text('Popular'),
),
ElevatedButton(
onPressed: () {},
child: const Text('New'),
),
ElevatedButton(
onPressed: () {},
child: const Text('Discount'),
),
],
)
19. Responsive Forms
Forms often become too wide on desktop screens. Use a maximum width.
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 500,
),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
TextField(
decoration: const InputDecoration(
labelText: 'Name',
),
),
const SizedBox(height: 16),
TextField(
decoration: const InputDecoration(
labelText: 'Email',
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {},
child: const Text('Submit'),
),
),
],
),
),
),
)
20. Responsive Login Screen
LayoutBuilder(
builder: (context, constraints) {
final isWide = constraints.maxWidth >= 800;
return Center(
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: isWide ? 900 : 500,
),
child: Padding(
padding: const EdgeInsets.all(24),
child: isWide
? Row(
children: const [
Expanded(child: LoginIllustration()),
Expanded(child: LoginForm()),
],
)
: const LoginForm(),
),
),
);
},
)
21. Responsive Text
Avoid unnecessarily large fixed text sizes on small screens. Prefer theme-based typography and allow text to wrap naturally.
Text(
'Welcome to Flutter',
style: Theme.of(context).textTheme.headlineMedium,
textAlign: TextAlign.center,
)
When users increase system text size for accessibility, the UI should remain usable rather than forcing a fixed text size.
22. Responsive Spacing
Spacing can also respond to available width.
final width = MediaQuery.sizeOf(context).width;
final padding = width < 600
? 16.0
: 32.0;
return Padding(
padding: EdgeInsets.all(padding),
child: const Text('Responsive content'),
);
Do not create dozens of arbitrary spacing breakpoints. Keep a small, consistent spacing system.
23. Break Large Widgets into Smaller Widgets
Large widgets become difficult to maintain when responsive logic is added. Break complex screens into smaller reusable widgets.
class DashboardPage extends StatelessWidget {
const DashboardPage({super.key});
@override
Widget build(BuildContext context) {
return const Column(
children: [
DashboardHeader(),
DashboardStats(),
DashboardChart(),
DashboardRecentOrders(),
],
);
}
}
Flutter's responsive best-practice guidance recommends breaking complex widgets into smaller widgets. This improves maintainability and can also reduce unnecessary rebuild work, especially when widgets can be made const. :contentReference[oaicite:13]{index=13}
24. Prefer const Widgets
Use const wherever a widget and its inputs are compile-time constants.
const Padding(
padding: EdgeInsets.all(16),
child: Text('Welcome'),
)
Using smaller const widgets can help Flutter reuse widget instances and keep widget rebuilds more efficient. :contentReference[oaicite:14]{index=14}
25. Support Touch, Mouse, Trackpad, and Keyboard
A responsive application should not only change its visual layout. It should also provide suitable interaction for different input methods.
- Touch users need comfortable tap targets.
- Mouse users may expect hover feedback.
- Trackpad users need smooth scrolling.
- Keyboard users need logical focus traversal.
- Desktop users may benefit from keyboard shortcuts.
Flutter's Material widgets provide many standard interaction behaviors, while custom widgets may need additional handling. :contentReference[oaicite:15]{index=15}
MouseRegion Example
MouseRegion(
onEnter: (_) {
setState(() {
isHovered = true;
});
},
onExit: (_) {
setState(() {
isHovered = false;
});
},
child: Container(
padding: const EdgeInsets.all(20),
child: const Text('Hover me'),
),
)
26. Support Keyboard Navigation
Users on desktop and users who depend on assistive technologies may rely on keyboard navigation.
Focus(
autofocus: true,
child: TextField(
decoration: const InputDecoration(
labelText: 'Search',
),
),
)
Built-in Material controls generally provide focus and keyboard traversal behavior, but custom widgets should be tested carefully. :contentReference[oaicite:16]{index=16}
27. Preserve Application State
Changing screen size, orientation, window size, or device configuration should not unnecessarily destroy user progress.
Examples of state that should be preserved include:
- Form input.
- Selected navigation item.
- Scroll position.
- Expanded/collapsed sections.
- Selected filters.
- Current page.
PageStorageKey Example
ListView.builder(
key: const PageStorageKey('products-list'),
itemCount: products.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(products[index]),
);
},
)
Flutter recommends preserving application state when the window changes size, rotates, or folds/unfolds. PageStorageKey can help preserve scroll state in appropriate list scenarios. :contentReference[oaicite:17]{index=17}
28. Do Not Lock Orientation Without a Strong Reason
Responsive applications should generally be able to handle different window sizes and orientations. Locking an application to one orientation can make future adaptive behavior more difficult and may reduce accessibility or multi-window flexibility. :contentReference[oaicite:18]{index=18}
Instead of designing only for portrait mode, test:
- Portrait phone.
- Landscape phone.
- Portrait tablet.
- Landscape tablet.
- Desktop window.
- Resized desktop window.
29. Avoid Horizontal Overflow
Horizontal overflow is one of the most common problems in responsive Flutter interfaces.
Problem
Row(
children: [
Container(width: 500, child: const Text('A')),
Container(width: 500, child: const Text('B')),
],
)
The combined width may exceed the available space.
Possible Solution
Row(
children: [
Expanded(
child: Container(
child: const Text('A'),
),
),
Expanded(
child: Container(
child: const Text('B'),
),
),
],
)
Another option is to use Wrap, a responsive grid, or a horizontal scroll view when horizontal scrolling is actually appropriate.
30. Use Scrollable Layouts Carefully
Small screens have limited vertical space. Long content should generally be placed inside an appropriate scrollable widget.
Scaffold(
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
const Text('Long Content'),
const SizedBox(height: 800),
ElevatedButton(
onPressed: () {},
child: const Text('Continue'),
),
],
),
),
),
)
For large lists, prefer lazy widgets such as ListView.builder rather than building every item at once.
31. Responsive Dashboard Example
class DashboardPage extends StatelessWidget {
const DashboardPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dashboard'),
),
body: SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
final width = constraints.maxWidth;
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Overview',
style: Theme.of(context)
.textTheme
.headlineSmall,
),
const SizedBox(height: 20),
GridView.count(
crossAxisCount: width < 600
? 1
: width < 1000
? 2
: 4,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: const [
StatCard(
title: 'Users',
value: '12,450',
),
StatCard(
title: 'Orders',
value: '3,240',
),
StatCard(
title: 'Revenue',
value: '₹8,45,000',
),
StatCard(
title: 'Pending',
value: '128',
),
],
),
],
),
);
},
),
),
);
}
}
class StatCard extends StatelessWidget {
final String title;
final String value;
const StatCard({
super.key,
required this.title,
required this.value,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(title),
const SizedBox(height: 8),
Text(
value,
style: Theme.of(context)
.textTheme
.headlineSmall,
),
],
),
),
);
}
}
32. Create a Reusable Responsive Layout Widget
When the same breakpoint logic is needed throughout an application, create a reusable widget.
class ResponsiveLayout extends StatelessWidget {
final Widget mobile;
final Widget tablet;
final Widget desktop;
const ResponsiveLayout({
super.key,
required this.mobile,
required this.tablet,
required this.desktop,
});
@override
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
if (width < 600) {
return mobile;
}
if (width < 1200) {
return tablet;
}
return desktop;
}
}
Usage
ResponsiveLayout(
mobile: const MobileDashboard(),
tablet: const TabletDashboard(),
desktop: const DesktopDashboard(),
)
33. Separate UI from Business Logic
Responsive behavior should not duplicate business logic.
For example, a shopping application may have three different layouts but the same product data:
class Product {
final String name;
final double price;
const Product({
required this.name,
required this.price,
});
}
Then the same product data can be passed to mobile, tablet, and desktop widgets.
ResponsiveLayout(
mobile: ProductList(products: products),
tablet: ProductGrid(products: products),
desktop: ProductGridWide(products: products),
)
This keeps the responsive layer focused on presentation while the data and business logic remain reusable.
34. Design to the Strengths of Each Form Factor
Responsive design does not mean that every device must have exactly the same interface.
- Mobile: prioritize quick actions, simplified navigation, and touch interaction.
- Tablet: take advantage of additional space for multi-column layouts.
- Desktop: support denser information displays, keyboard interaction, hover behavior, and side navigation where appropriate.
- Web: consider browser navigation, deep links, URL-based navigation, and flexible window sizes.
Flutter recommends considering the strengths and capabilities of each form factor rather than simply duplicating the exact same interface everywhere. :contentReference[oaicite:19]{index=19}
35. Consider Large Screens Carefully
Large screens include tablets, foldables, ChromeOS devices, iPads, desktop applications, and web applications. A large display provides more space, but simply stretching the mobile UI can create poor usability. :contentReference[oaicite:20]{index=20}
Useful large-screen techniques include:
- Maximum content widths.
- Multi-column layouts.
- Navigation rails or sidebars.
- List-detail layouts.
- Responsive grids.
- More information displayed simultaneously.
- Keyboard and mouse support.
36. List-Detail Responsive Pattern
A common application pattern is showing a list and selected details.
Small Screen
ListPage → DetailPage
Large Screen
Row(
children: [
SizedBox(
width: 300,
child: ListPage(),
),
const VerticalDivider(width: 1),
Expanded(
child: DetailPage(),
),
],
)
This pattern is useful for email applications, contact applications, messaging applications, product catalogs, and admin panels.
37. Responsive Dialogs
Dialogs should not become excessively wide on desktop screens.
Dialog(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 500,
),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Edit Profile'),
const SizedBox(height: 16),
const TextField(),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {},
child: const Text('Save'),
),
],
),
),
),
)
38. Responsive Images
Images should adapt to their available space and preserve their aspect ratio.
AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(
imageUrl,
fit: BoxFit.cover,
),
)
For large layouts, the image can occupy one section of a row, while on small screens it can move above the text.
39. Use Theme Instead of Hard-Coding Styles
A responsive application is easier to maintain when typography, colors, shapes, and spacing are centralized.
MaterialApp(
theme: ThemeData(
colorSchemeSeed: Colors.blue,
useMaterial3: true,
),
home: const HomePage(),
)
Then use the theme throughout the application:
Text(
'Dashboard',
style: Theme.of(context).textTheme.headlineMedium,
)
40. Test Different Window Sizes
Responsive design should be tested at more than one screen size.
- Small phone width.
- Large phone width.
- Tablet width.
- Desktop width.
- Very wide desktop window.
- Very narrow resized window.
- Portrait orientation.
- Landscape orientation.
- Split-screen or constrained windows.
- Different text scaling settings.
- Touch input.
- Mouse input.
- Keyboard navigation.
41. Common Responsive Flutter Mistakes
Mistake 1: Using Too Many Fixed Widths
Fixed widths can cause overflow when the available space changes.
Mistake 2: Checking Device Type
Do not make layout decisions based on whether the device is a phone or tablet. Use available window size instead. :contentReference[oaicite:21]{index=21}
Mistake 3: Using Orientation as the Main Breakpoint
Orientation alone does not tell you how much space the application actually has.
Mistake 4: Stretching Everything on Desktop
Large screens should not automatically result in extremely wide text fields or paragraphs.
Mistake 5: Ignoring Keyboard and Mouse
Desktop and web applications should support appropriate input methods.
Mistake 6: Losing State During Resize
Responsive changes should not unnecessarily reset forms, selections, or scroll positions.
Mistake 7: Creating One Huge Widget
Break complex interfaces into smaller widgets to make responsive behavior easier to manage.
42. Responsive Flutter Best Practices Checklist
- Use available window size to make layout decisions.
- Use
MediaQuery.sizeOf for window-level information.
- Use
LayoutBuilder for local parent constraints.
- Choose breakpoints based on layout requirements.
- Avoid device-type checks for layout decisions.
- Avoid orientation-only layout logic.
- Avoid unnecessary fixed widths.
- Use
Expanded and Flexible where appropriate.
- Use
Wrap for items that can move between lines.
- Use responsive grids.
- Limit maximum content width on large screens.
- Use
SafeArea where appropriate.
- Support touch, mouse, trackpad, and keyboard input.
- Preserve important application state.
- Break large widgets into smaller reusable widgets.
- Use
const widgets where possible.
- Test multiple screen sizes and window configurations.
- Design for the strengths of different form factors.
43. Practical Responsive App Workflow
- Identify the important content and user actions.
- Build a simple mobile-friendly layout.
- Identify where the mobile layout becomes crowded.
- Create meaningful breakpoints at those points.
- Use
LayoutBuilder or MediaQuery.sizeOf.
- Adapt navigation when necessary.
- Adapt grids and columns.
- Limit content width on large screens.
- Add mouse and keyboard interactions.
- Preserve application state.
- Test resizing, orientation changes, and multiple form factors.
- Refactor repeated responsive logic into reusable widgets.
44. Complete Responsive Example
import 'package:flutter/material.dart';
void main() {
runApp(const ResponsiveApp());
}
class ResponsiveApp extends StatelessWidget {
const ResponsiveApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorSchemeSeed: Colors.blue,
useMaterial3: true,
),
home: const ResponsiveHomePage(),
);
}
}
class ResponsiveHomePage extends StatelessWidget {
const ResponsiveHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Responsive Flutter'),
),
body: SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
final width = constraints.maxWidth;
if (width < 600) {
return const MobileContent();
}
if (width < 1000) {
return const TabletContent();
}
return const DesktopContent();
},
),
),
);
}
}
class MobileContent extends StatelessWidget {
const MobileContent({super.key});
@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(16),
children: const [
Text(
'Mobile Layout',
style: TextStyle(fontSize: 24),
),
SizedBox(height: 16),
Card(child: ListTile(title: Text('Dashboard'))),
Card(child: ListTile(title: Text('Products'))),
Card(child: ListTile(title: Text('Orders'))),
],
);
}
}
class TabletContent extends StatelessWidget {
const TabletContent({super.key});
@override
Widget build(BuildContext context) {
return GridView.count(
padding: const EdgeInsets.all(24),
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
children: const [
DashboardCard(title: 'Users'),
DashboardCard(title: 'Orders'),
DashboardCard(title: 'Revenue'),
DashboardCard(title: 'Products'),
],
);
}
}
class DesktopContent extends StatelessWidget {
const DesktopContent({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 1200,
),
child: Row(
children: const [
SizedBox(
width: 240,
child: DashboardSidebar(),
),
VerticalDivider(width: 1),
Expanded(
child: DesktopDashboard(),
),
],
),
),
);
}
}
class DashboardSidebar extends StatelessWidget {
const DashboardSidebar({super.key});
@override
Widget build(BuildContext context) {
return ListView(
children: const [
ListTile(
leading: Icon(Icons.dashboard),
title: Text('Dashboard'),
),
ListTile(
leading: Icon(Icons.shopping_cart),
title: Text('Orders'),
),
ListTile(
leading: Icon(Icons.people),
title: Text('Users'),
),
],
);
}
}
class DesktopDashboard extends StatelessWidget {
const DesktopDashboard({super.key});
@override
Widget build(BuildContext context) {
return GridView.count(
padding: const EdgeInsets.all(24),
crossAxisCount: 2,
crossAxisSpacing: 20,
mainAxisSpacing: 20,
children: const [
DashboardCard(title: 'Users'),
DashboardCard(title: 'Orders'),
DashboardCard(title: 'Revenue'),
DashboardCard(title: 'Products'),
],
);
}
}
class DashboardCard extends StatelessWidget {
final String title;
const DashboardCard({
super.key,
required this.title,
});
@override
Widget build(BuildContext context) {
return Card(
child: Center(
child: Text(
title,
style: Theme.of(context).textTheme.titleLarge,
),
),
);
}
}
45. Official Flutter Resources
46. Flutter Course Resources
For structured Flutter learning and training resources, visit:
47. Quick Revision
| Concept | Best Practice |
|---|
| Screen measurement | Use MediaQuery.sizeOf or LayoutBuilder. |
| Local responsive widget | Use LayoutBuilder. |
| Breakpoints | Choose them based on layout requirements. |
| Device detection | Avoid using phone/tablet checks for layout. |
| Orientation | Do not use orientation alone for major layouts. |
| Large screens | Use max-width constraints and multi-column layouts. |
| Navigation | Adapt NavigationBar, NavigationRail, or sidebar according to available space. |
| Grid | Use available width and maximum item dimensions. |
| Spacing | Use a consistent spacing system. |
| Input | Support touch, mouse, trackpad, and keyboard. |
| State | Preserve important application and scroll state. |
| Accessibility | Respect text scaling, focus, keyboard navigation, and usable hit areas. |
| Testing | Test multiple sizes, orientations, and window configurations. |
48. Key Takeaways
- Responsive Flutter design is about adapting UI to the space actually available.
- Adaptive design goes further by changing UI structures and interactions when necessary.
- Use
MediaQuery.sizeOf for application-window measurements.
- Use
LayoutBuilder when a component should respond to its parent's constraints.
- Do not assume that phone, tablet, desktop, portrait, or landscape automatically represents a particular amount of space.
- Use flexible widgets such as
Expanded, Flexible, and Wrap.
- Use responsive grids and maximum content widths.
- Use
SafeArea where content needs protection from system UI and display cutouts.
- Support touch, mouse, keyboard, and accessibility requirements.
- Preserve user state during resizing and configuration changes.
- Break complex interfaces into smaller reusable widgets.
- Test your application across different window sizes instead of testing only one phone.
Following these practices helps create Flutter applications that remain usable, maintainable, and visually consistent across phones, tablets, foldables, desktops, and web environments.