| 1 | --- |
| 2 | name: flutter-build-responsive-layout |
| 3 | description: Use `LayoutBuilder`, `MediaQuery`, or `Expanded/Flexible` to create a layout that adapts to different screen sizes. Use when you need the UI to look good on both mobile and tablet/desktop form factors. |
| 4 | metadata: |
| 5 | model: models/gemini-3.1-pro-preview |
| 6 | last_modified: Tue, 21 Apr 2026 20:17:40 GMT |
| 7 | --- |
| 8 | # Implementing Adaptive Layouts |
| 9 | |
| 10 | ## Contents |
| 11 | - [Space Measurement Guidelines](#space-measurement-guidelines) |
| 12 | - [Widget Sizing and Constraints](#widget-sizing-and-constraints) |
| 13 | - [Device and Orientation Behaviors](#device-and-orientation-behaviors) |
| 14 | - [Workflow: Constructing an Adaptive Layout](#workflow-constructing-an-adaptive-layout) |
| 15 | - [Workflow: Optimizing for Large Screens](#workflow-optimizing-for-large-screens) |
| 16 | - [Examples](#examples) |
| 17 | |
| 18 | ## Space Measurement Guidelines |
| 19 | Determine the available space accurately to ensure layouts adapt to the app window, not just the physical device. |
| 20 | |
| 21 | * **Use `MediaQuery.sizeOf(context)`** to get the size of the entire app window. |
| 22 | * **Use `LayoutBuilder`** to make layout decisions based on the parent widget's allocated space. Evaluate `constraints.maxWidth` to determine the appropriate widget tree to return. |
| 23 | * **Do not use `MediaQuery.orientationOf` or `OrientationBuilder`** near the top of the widget tree to switch layouts. Device orientation does not accurately reflect the available app window space. |
| 24 | * **Do not check for hardware types** (e.g., "phone" vs. "tablet"). Flutter apps run in resizable windows, multi-window modes, and picture-in-picture. Base all layout decisions strictly on available window space. |
| 25 | |
| 26 | ## Widget Sizing and Constraints |
| 27 | Understand and apply Flutter's core layout rule: **Constraints go down. Sizes go up. Parent sets position.** |
| 28 | |
| 29 | * **Distribute Space:** Use `Expanded` and `Flexible` within `Row`, `Column`, or `Flex` widgets. |
| 30 | * Use `Expanded` to force a child to fill all remaining available space (equivalent to `Flexible` with `fit: FlexFit.tight` and a `flex` factor of 1.0). |
| 31 | * Use `Flexible` to allow a child to size itself up to a specific limit while still expanding/contracting. Use the `flex` factor to define the ratio of space consumption among siblings. |
| 32 | * **Constrain Width:** Prevent widgets from consuming all horizontal space on large screens. Wrap widgets like `GridView` or `ListView` in a `ConstrainedBox` or `Container` and define a `maxWidth` in the `BoxConstraints`. |
| 33 | * **Lazy Rendering:** Always use `ListView.builder` or `GridView.builder` when rendering lists with an unknown or large number of items. |
| 34 | |
| 35 | ## Device and Orientation Behaviors |
| 36 | Ensure the app behaves correctly across all device form factors and input methods. |
| 37 | |
| 38 | * **Do not lock screen orientation.** Locking orientation causes severe layout issues on foldable devices, often resulting in letterboxing (the app centered with black borders). Android large format tiers require both portrait and landscape support. |
| 39 | * **Fallback for Locked Orientation:** If business requirements strictly mandate a locked orientation, use the `Display API` to retrieve physical screen dimensions instead of `MediaQuery`. `MediaQuery` fails to receive the larger window size in compatibility modes. |
| 40 | * **Support Multiple Inputs:** Implement support for basic mice, trackpads, and keyboard shortcuts. Ensure touch targets are appropriately sized and keyboard navigation is accessible. |
| 41 | |
| 42 | ## Workflow: Constructing an Adaptive Layout |
| 43 | |
| 44 | Follow this workflow to implement a layout that adapts to the available `BoxConstraints`. |
| 45 | |
| 46 | **Task Progress:** |
| 47 | - [ ] Identify the target widget that requires adaptive behavior. |
| 48 | - [ ] Wrap the widget tree in a `LayoutBuilder`. |
| 49 | - [ ] Extract the `constraints.maxWidth` from the builder callback. |
| 50 | - [ ] Define an adaptive breakpoint (e.g., `largeScreenMinWidth = 600`). |
| 51 | - [ ] **If `maxWidth > largeScreenMinWidth`:** Return a large-screen layout (e.g., a `Row` placing a navigation sidebar and content area side-by-side). |
| 52 | - [ ] **If `maxWidth <= largeScreenMinWidth`:** Return a small-screen layout (e.g., a `Column` or standard navigation-style approach). |
| 53 | - [ ] Run validator -> resize the application window -> review layout transitions -> fix overflow errors. |
| 54 | |
| 55 | ## Workflow: Optimizing for Large Screens |
| 56 | |
| 57 | Follow this workflow to prevent UI elements from stretching unnaturally on large displays. |
| 58 | |
| 59 | **Task Progress:** |
| 60 | - [ ] Identify full-width components (e.g., `ListView`, text blocks, forms). |
| 61 | - [ ] **If optimizing a list:** Convert `ListView.builder` to `GridView.builder` using `SliverGridDelegateWithMaxCrossAxisExtent` to automatically adjust column counts based on window size. |
| 62 | - [ ] **If optimizing a form or text block:** Wrap the component in a `ConstrainedBox`. |
| 63 | - [ ] Apply `BoxConstraints(maxWidth: [optimal_width])` to the `ConstrainedBox`. |
| 64 | - [ ] Wrap the `ConstrainedBox` in a `Center` widget to keep the constrained content centered on large screens. |
| 65 | - [ ] Run validator -> test on desktop/tablet target -> review horizontal stretching -> adjust `maxWidth` or grid extents. |
| 66 | |
| 67 | ## Examples |
| 68 | |
| 69 | ### Adaptive Layout using LayoutBuilder |
| 70 | Demonstrates switching between a mobile and desktop layout based on available width. |
| 71 | |
| 72 | ```dart |
| 73 | import 'package:flutter/material.dart'; |
| 74 | |
| 75 | const double largeScreenMinWidth = 600.0; |
| 76 | |
| 77 | class AdaptiveLayout extends StatelessWidget { |
| 78 | const AdaptiveLayout({super.key}); |
| 79 | |
| 80 | @override |
| 81 | Widget build(BuildContext context) { |
| 82 | return LayoutBuilder( |
| 83 | builder: (context, constraints) { |
| 84 | if (constraints.maxWidth > largeScreenMinWidth) { |
| 85 | return _buildLargeScreenLayout(); |
| 86 | } else { |
| 87 | return _buildSmallScreenLayout(); |
| 88 | } |
| 89 | }, |
| 90 | ); |
| 91 | } |
| 92 | |
| 93 | Widget _buildLargeScreenLayout() { |
| 94 | return Row( |
| 95 | children: [ |
| 96 | const SizedBox(width: 250, child: Placeholder(color: Colors.blue)), |
| 97 | const VerticalDivider(width: 1), |
| 98 | Expanded(child: const Placeholder(color: Colors.green)), |
| 99 | ], |
| 100 | ); |
| 101 | } |
| 102 | |
| 103 | Widget _buildSmallScreenLayout() { |
| 104 | return const Placeholder(color: Colors.green); |
| 105 | } |
| 106 | } |
| 107 | ``` |
| 108 | |
| 109 | ### Constraining Width on Large Screens |
| 110 | Demonstrates preventing a widget from consuming all horizontal space. |
| 111 | |
| 112 | ```dart |
| 113 | import 'package:flutter/material.dart'; |
| 114 | |
| 115 | class ConstrainedContent extends StatelessWidget { |
| 116 | const ConstrainedContent({super.key}); |
| 117 | |
| 118 | @override |
| 119 | Widget build(BuildContext context) { |
| 120 | return Scaffold( |
| 121 | body: Center( |
| 122 | child: ConstrainedBox( |
| 123 | constraints: const BoxConstraints( |
| 124 | maxWidth: 800.0, // Maximum width for readability |
| 125 | ), |
| 126 | child: ListView.builder( |
| 127 | itemCount: 50, |
| 128 | itemBuilder: (context, index) { |
| 129 | return ListTile( |
| 130 | title: Text('Item $index'), |
| 131 | ); |
| 132 | }, |
| 133 | ), |
| 134 | ), |
| 135 | ), |
| 136 | ); |
| 137 | } |
| 138 | } |
| 139 | ``` |