Hey everyone, welcome! In this step-by-step guide, you’ll learn how to build responsive layout in Flutter from scratch so your app looks great on phones, tablets, and desktops alike.
Mastering responsive Flutter app design is a game-changer. Imagine writing your codebase once and having it adapt seamlessly to any device size—it saves tons of development time and keeps your UI looking sharp.
Whether you're completely new to Flutter or just want a solid refresher on handling different display sizes, this beginner-friendly walk-through has you covered.
Let’s jump right in!
Step 1: Setting up your Flutter project
First, let's set up a clean workspace. Open your terminal, navigate to your Desktop, and run the standard command to create a new Flutter project for responsive design:
cd Desktop
flutter create responsive_layout
Once the setup finishes, jump into the newly created folder and open it inside VS Code:
cd responsive_layout
code .
Now that you're inside VS Code, head over to the lib folder and open the main.dart file. To ensure this Flutter layout tutorial for beginners is completely hands-on, go ahead and delete all the default starter code in main.dart.
We'll build everything cleanly from scratch!
Ready to Build Real-World Apps?
Master Flutter UI engineering with 100+ hands-on lessons, production projects, and lifetime access.
Step 2: Building the entry point and main app widget
Now that our main.dart file is completely blank, let's write the starting point for our application. We’ll begin by importing the standard Material library, defining the main() function, and running our primary MyApp widget.
Add this code to your main.dart file:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
Next, let's build the StatelessWidget for MyApp. This is where we configure our Flutter MaterialApp setup for responsive UI, including the app title, theme settings, and primary home route:
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Responsive Layout',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.blue,
brightness: Brightness.light,
),
home: const HomeScreen(),
);
}
}
Awesome! With our base app configuration in place, the next step is to create the HomeScreen() widget to start laying out our responsive UI elements.
Step 3: Creating the HomeScreen Scaffold
To keep our project organized as it grows, let's follow Flutter project folder structure best practices rather than dumping everything inside main.dart.
Create a new folder named screens inside your lib directory, and add a file called home_screen.dart inside it. Your project tree should look like this:
lib/
├── screens/
│ └── home_screen.dart
└── main.dart
Now open home_screen.dart and build our HomeScreen widget. We’ll use a StatefulWidget here so we can manage dynamic layout updates later:
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold();
}
}
Next, head back to main.dart and import your newly created file at the top so the app can recognize HomeScreen():
import 'package:flutter/material.dart';
import 'package:responsive_layout/screens/home_screen.dart';
void main() {
runApp(const MyApp());
}
Now it's time to run your Flutter app for desktop debugging! Select Windows (windows-x64) (or your preferred OS target) from the bottom toolbar in VS Code, and hit F5 (or click Start Debugging).
You’ll see a blank white screen launch—that’s completely normal! Our scaffolding is ready, and we’re set to start building the actual UI.
Step 4: Creating a reusable custom card widget
Instead of hardcoding individual UI components inside our screens, let's learn how to create reusable custom widgets in Flutter. Building modular UI components keeps your codebase clean, easy to maintain, and simple to adapt across different screen sizes.
Inside your lib directory, create a new folder named widgets. Inside that folder, create a new file named stat_card.dart.
Your updated project tree will look like this:
lib/
├── screens/
│ └── home_screen.dart
├── widgets/
│ └── stat_card.dart
└── main.dart
Now, let's write the code for our StatCard widget inside stat_card.dart. We'll pass in dynamic parameters for an icon, title, and numerical value so we can reuse this single component across our entire dashboard layout:
import 'package:flutter/material.dart';
class StatCard extends StatelessWidget {
final IconData icon;
final String title;
final String value;
const StatCard({
super.key,
required this.icon,
required this.title,
required this.value,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: theme.colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, size: 28),
const Spacer(),
Text(title, style: theme.textTheme.titleMedium),
const SizedBox(height: 4),
Text(
value,
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
);
}
}
This simple, self-contained Flutter stat card dashboard widget UI gives us everything we need to assemble a clean grid layout.
Step 5: Implementing LayoutBuilder for responsive design
When deciding how to build a responsive layout in Flutter, developers usually choose between MediaQuery and LayoutBuilder. While both are great, LayoutBuilder is widely considered the best practice for component-level responsiveness.
Unlike MediaQuery (which looks at the entire screen size), LayoutBuilder checks the exact width constraints of its parent widget—making your layout logic much more modular and reusable!
Let's put this into practice inside home_screen.dart. Wrap the body of your Scaffold with a LayoutBuilder:
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
final double width = constraints.maxWidth;
return Center(
child: Text(
'Current Width: ${width}px',
style: TextStyle(fontSize: 16),
),
);
},
),
);
If you resize your app window right now, you’ll see the pixel value update live on your screen!
By reading constraints.maxWidth, we can easily establish Flutter screen width breakpoints to dynamically swap layouts between mobile, tablet, and desktop viewports.
Step 6: Setting up responsive breakpoints
The simplest way to structure our UI is by using basic rows and columns. For this tutorial, we will define Flutter screen size breakpoints for mobile, tablet, and desktop environments to ensure our app looks perfect on any device.
To handle these transitions dynamically, we can use straightforward Flutter LayoutBuilder if else conditions. Let's update our code to include these logical branches:
body: LayoutBuilder(
builder: (context, constraints) {
final double width = constraints.maxWidth;
if (width < 600) {
// Mobile layout
return const Center(child: Text('Mobile Layout'));
} else if (width < 900) {
// Tablet layout
return const Center(child: Text('Tablet Layout'));
} else {
// Desktop layout
return Center(child: Text('Desktop Layout'));
}
},
),
With this setup, you have a solid foundation for a mobile, tablet, and desktop responsive design in Flutter.
The logic is simple:
- Under 600px: Renders the mobile view.
- 600px to 899px: Snaps to the tablet view.
- 900px and above: Triggers the full desktop experience.
Go ahead and resize your app window—you will see the text swap out in real-time!
Step 7: Separating layout views into dedicated files
Even though we created a reusable StatCard widget, keeping all our layout logic inside home_screen.dart can make the file messy very quickly.
To maintain clean code responsive design in Flutter, we should split each screen layout into its own dedicated widget file. Let's organize our project directory by creating a new breakpoints (or layouts) folder inside lib:
lib/
├── breakpoints/
│ ├── mobile.dart
│ ├── tablet.dart
│ └── desktop.dart
├── screens/
│ └── home_screen.dart
├── widgets/
│ └── stat_card.dart
└── main.dart
This structure lets you build and maintain your mobile, tablet, and desktop UIs independently without cluttering your main screens.
Now, your home_screen.dart simply acts as a dispatcher:
body: LayoutBuilder(
builder: (context, constraints) {
final double width = constraints.maxWidth;
if (width < 600) {
return const Mobile();
} else if (width < 900) {
return const Tablet();
} else {
return const Desktop();
}
},
),
This clean abstraction is one of the most effective Flutter responsive architecture best practices. Now that our workspace is organized, let's start building the mobile layout first!
Step 8: Building the mobile layout with flexible Bento styling
Now let's open mobile.dart and build our responsive mobile UI using Row and Column in Flutter. We’ll organize our dashboard into three vertical sections holding custom metric cards:
- Top Section: Students, Subscribers, and Courses count
- Middle Section: Revenue metrics
- Bottom Section: Reviews and Upcoming tasks
To prevent fixed heights from breaking across different mobile screens and to give our dashboard a sleek modern feel, we’ll take advantage of the flex property inside Expanded widgets.
This lets us build a dynamic, bento grid layout in Flutter where each row grows proportionally relative to the others.
Here is the complete code for mobile.dart:
import 'package:flutter/material.dart';
import 'package:responsive_layout/widgets/stat_card.dart';
class Mobile extends StatelessWidget {
const Mobile({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// Section 1: Top Metrics (Flex ratio: 1)
Expanded(
flex: 1,
child: Row(
children: const [
Expanded(
child: StatCard(
icon: Icons.people,
title: 'Students',
value: '832',
),
),
SizedBox(width: 16),
Expanded(
child: StatCard(
icon: Icons.people_outline,
title: 'Subscribers',
value: '1.2k',
),
),
],
),
),
const SizedBox(height: 16),
Expanded(
flex: 1,
child: Row(
children: const [
Expanded(
child: StatCard(
icon: Icons.grade,
title: 'Courses',
value: '12',
),
),
],
),
),
// Section 2: Featured Hero Card (Flex ratio: 2 for extra height)
const SizedBox(height: 16),
Expanded(
flex: 2,
child: Row(
children: const [
Expanded(
child: StatCard(
icon: Icons.attach_money,
title: 'Revenue',
value: '\$5,260',
),
),
],
),
),
// Section 3: Bottom Metrics (Flex ratio: 1)
const SizedBox(height: 16),
Expanded(
flex: 1,
child: Row(
children: const [
Expanded(
child: StatCard(
icon: Icons.star,
title: 'Reviews',
value: '4.9',
),
),
SizedBox(width: 16),
Expanded(
child: StatCard(
icon: Icons.check_box,
title: 'Upcoming',
value: '72',
),
),
],
),
),
],
),
);
}
}
By assigning flex: 2 to our middle section while keeping the upper and lower sections at flex: 1, the Revenue card automatically expands to twice the vertical space of the standard rows.
Learning how to build mobile layout in Flutter using Expanded flex ensures that whether your app runs on a compact iPhone or a long Android device, the grid scales perfectly without overflow errors!
Want to Design Like a Senior Dev?
Unlock 100+ project-based tutorials, advanced responsive techniques, and lifetime course updates.
Step 9: Building the tablet layout
Now let's open tablet.dart. The beauty of building modular UI components is that we don't have to rewrite our widgets from scratch—we are simply reusing widgets for our Flutter responsive dashboard and rearranging them to fit wider screen dimensions!
For our tablet view, we’ll organize our UI into two main horizontal rows rather than three vertical sections:
- Top Row: Displays Students, Subscribers, and Courses side-by-side across three equal columns.
- Bottom Row: Combines a wide Revenue feature card on the left with a stacked column for Reviews and Upcoming tasks on the right.
Here is the code for tablet.dart:
import 'package:flutter/material.dart';
import 'package:responsive_layout/widgets/stat_card.dart';
class Tablet extends StatelessWidget {
const Tablet({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// Section 1: Top Metrics (Row of 3 cards)
Expanded(
flex: 1,
child: Row(
children: const [
Expanded(
child: StatCard(
icon: Icons.people,
title: 'Students',
value: '832',
),
),
SizedBox(width: 16),
Expanded(
child: StatCard(
icon: Icons.people_outline,
title: 'Subscribers',
value: '1.2k',
),
),
SizedBox(width: 16),
Expanded(
child: StatCard(
icon: Icons.grade,
title: 'Courses',
value: '12',
),
),
],
),
),
// Section 2: Bottom Dashboard Area
const SizedBox(height: 16),
Expanded(
flex: 2,
child: Row(
children: [
const Expanded(
flex: 2,
child: StatCard(
icon: Icons.attach_money,
title: 'Revenue',
value: '\$5,260',
),
),
const SizedBox(width: 16),
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
Expanded(
child: StatCard(
icon: Icons.star,
title: 'Reviews',
value: '4.9',
),
),
SizedBox(height: 16),
Expanded(
child: StatCard(
icon: Icons.check_box,
title: 'Upcoming',
value: '72',
),
),
],
),
),
],
),
),
],
),
);
}
}
By nesting a Column inside an Expanded row element, you can master how to build responsive tablet layouts in Flutter using flex ratios (flex: 2 vs flex: 1) to achieve clean, adaptive designs without extra layout libraries.
Step 10: Building the desktop layout
For our widescreen view inside desktop.dart, we’ll take a different architectural approach than our mobile and tablet setups.
Instead of stacking sections vertically, we will learn how to build responsive desktop layout in Flutter by splitting our space horizontally using a primary Row containing two main columns:
-
Left Column (
flex: 1): A compact sidebar containing all our primary stat cards (Students, Subscribers, Courses, Revenue, and Reviews). -
Right Area (
flex: 3): A wide hero panel dedicated entirely to displaying the Upcoming tasks.
Here is the complete code for desktop.dart:
import 'package:flutter/material.dart';
import 'package:responsive_layout/widgets/stat_card.dart';
class Desktop extends StatelessWidget {
const Desktop({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
// Left Column: Quick Metrics Sidebar (Flex ratio: 1)
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
Expanded(
child: StatCard(
icon: Icons.people,
title: 'Students',
value: '832',
),
),
SizedBox(height: 16),
Expanded(
child: StatCard(
icon: Icons.people_outline,
title: 'Subscribers',
value: '1.2k',
),
),
SizedBox(height: 16),
Expanded(
child: StatCard(
icon: Icons.grade,
title: 'Courses',
value: '12',
),
),
SizedBox(height: 16),
Expanded(
child: StatCard(
icon: Icons.attach_money,
title: 'Revenue',
value: '\$5,260',
),
),
SizedBox(height: 16),
Expanded(
child: StatCard(
icon: Icons.star,
title: 'Reviews',
value: '4.9',
),
),
],
),
),
// Right Area: Main Task Workstation (Flex ratio: 3)
const SizedBox(width: 16),
const Expanded(
flex: 3,
child: StatCard(
icon: Icons.check_box,
title: 'Upcoming',
value: '72',
),
),
],
),
);
}
}
By assigning flex: 3 to the main focus card on the right while giving flex: 1 to the left metrics column, you create a balanced, spacious desktop dashboard.
Mastering this pattern gives you full control over Flutter desktop UI design with Row and Column structures.
Testing your final responsive output
With all three layout files created and hooked up to our LayoutBuilder, it’s time to see the magic in action!
Hit Hot Reload (or restart your app) and try dragging your desktop application window to resize it.
Notice how your interface dynamically adapts as you cross each breakpoint:
- Mobile view (<600px): Stacks metric cards in dynamic vertical rows.
- Tablet view (600px–899px): Reorganizes cards side-by-side using horizontal flex spaces.
- Desktop view (≥900px): Transforms into a widescreen dual-column dashboard.
Testing responsive UI in Flutter like this demonstrates how powerful clean architecture really is. Not only do you have a fully adaptive layout, but your codebase is modular, organized, and super easy to customize down the road.
Building adaptive user interfaces is a core skill for modern cross-platform developers. If you want to dive deeper into advanced design patterns, layout strategies, and real-world app architecture, check out our complete Flutter UI engineering course for step-by-step masterclasses!
I encourage you to experiment with your own layout ideas using Row, Column, and Expanded. Sometimes the most impressive dashboards are built with the simplest widgets.
Thanks for following along, and I'll see you in the next tutorial!
Take Your Flutter Skills Further
Build production-ready apps from scratch with 100+ practical lessons and lifetime video updates.





Top comments (0)