Skip to main content
Back to all tutorials
Tested: Flutter 3.24.3 • Dart 3.5.3
T08.01 Verified September 2026
LayoutBuilder GridView Responsive Breakpoints Adaptive MediaQuery

Build Responsive LayoutBuilder and Adaptive GridView Layouts

Construct adaptive multi-screen dashboards that gracefully adjust column counts, padding, and font scales across 360px mobile, 768px tablet, and 1440px desktop displays.

1. The Core Challenge & Problem

Hardcoding fixed column counts (e.g. crossAxisCount: 3) or absolute pixel widths causes devastating layout bugs. On compact 360px mobile devices, three columns become unreadably squished and trigger yellow RenderFlex overflow stripes. Conversely, on wide 1440px desktop monitors, the same cards stretch unnaturally into giant banners with distorted aspect ratios.

2. Architectural Principles & Resolution

Modern adaptive Flutter architecture utilizes two complementary techniques: 1. **LayoutBuilder Over MediaQuery**: Prefer `LayoutBuilder` over `MediaQuery.of(context).size.width` when designing components. `MediaQuery` returns the full window width, which breaks when your component is placed inside split-screen tablet drawers, desktop sidebars, or modal dialogs. `LayoutBuilder` provides the exact `constraints.maxWidth` available to that specific widget. 2. **SliverGridDelegateWithMaxCrossAxisExtent**: Instead of fixed column counts, use `maxCrossAxisExtent: 280`. The Flutter framework automatically calculates the optimal number of columns based on available space (1 column on mobile, 2 on tablet, 4 on desktop) without hardcoded switch statements. 3. **Adaptive Breakpoint Tokens**: Group breakpoint thresholds into clean constants: - Compact / Mobile: < 600px - Medium / Tablet: 600px – 1024px - Expanded / Desktop: > 1024px

3. Complete Tested Flutter Code

main.dart (Flutter 3.x+ ready)
lib/main.dart Copy & Paste into a new Flutter project
import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: ResponsiveDashboardPage()));

class ResponsiveDashboardPage extends StatelessWidget {
  const ResponsiveDashboardPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('T08.01: Responsive LayoutBuilder')),
      body: LayoutBuilder(
        builder: (context, constraints) {
          // Use parent constraints rather than full window dimensions!
          final isCompact = constraints.maxWidth < 600;
          final isTablet = constraints.maxWidth >= 600 && constraints.maxWidth < 1024;
          final padding = isCompact ? 12.0 : isTablet ? 20.0 : 32.0;

          return Padding(
            padding: EdgeInsets.all(padding),
            child: CustomScrollView(
              slivers: [
                SliverToBoxAdapter(
                  child: Padding(
                    padding: const EdgeInsets.only(bottom: 16),
                    child: Text(
                      isCompact ? 'Mobile View (< 600px)' : isTablet ? 'Tablet View (600px - 1024px)' : 'Desktop View (> 1024px)',
                      style: TextStyle(
                        fontSize: isCompact ? 18 : 24,
                        fontWeight: FontWeight.bold,
                        color: const Color(0xFF0F172A),
                      ),
                    ),
                  ),
                ),
                // SliverGrid with maxCrossAxisExtent auto-computes optimal column counts!
                SliverGrid(
                  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
                    maxCrossAxisExtent: 260.0,
                    mainAxisSpacing: 16.0,
                    crossAxisSpacing: 16.0,
                    childAspectRatio: 1.2,
                  ),
                  delegate: SliverChildBuilderDelegate(
                    (context, index) {
                      return Container(
                        decoration: BoxDecoration(
                          color: Colors.white,
                          borderRadius: BorderRadius.circular(16),
                          border: Border.all(color: const Color(0xFFE2E8F0)),
                          boxShadow: const [
                            BoxShadow(color: Color(0x0A000000), blurRadius: 10, offset: Offset(0, 4)),
                          ],
                        ),
                        padding: const EdgeInsets.all(16),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            CircleAvatar(
                              backgroundColor: const Color(0xFFEFF6FF),
                              child: Icon(Icons.dashboard_customize, color: const Color(0xFF2563EB)),
                            ),
                            const Spacer(),
                            Text('Module ${index + 1}', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
                            const SizedBox(height: 4),
                            Text('Adaptive Card', style: TextStyle(color: Colors.grey[600], fontSize: 13)),
                          ],
                        ),
                      );
                    },
                    childCount: 8,
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

4. Expected Visual & Behavioral Result

Resizing the window dynamically transitions the grid from 1 column on narrow mobile (360px) to 2-3 columns on tablet (768px), and 4+ columns on wide desktop (1440px+), maintaining comfortable 16px margins with zero overflow.

5. Common Pitfalls & Traps

Pitfall #1: Using MediaQuery.of(context).size.width inside reusable cards or sidebar components

Remedy: MediaQuery returns whole-window width, causing nested cards to think they have 1440px of width even when placed inside a 300px sidebar drawer. Always use LayoutBuilder for component-level constraints.

Pitfall #2: Setting fixed pixel crossAxisSpacing without scaling padding

Remedy: Use maxCrossAxisExtent to allow the grid engine to compute ideal item counts automatically.

Pitfall #3: Nesting an unconstrained GridView inside a Column without shrinkWrap or Expanded

Remedy: Always place grids in an Expanded widget or use CustomScrollView with SliverGrid to ensure proper bounded scrolling.

Official Flutter References & Standards