Flutter Adaptive Layout Widgets: Building UIs That Fit Any Screen
A practical guide to Flutter's adaptive and responsive layout toolkit — MediaQuery, LayoutBuilder, and the widgets that make one codebase usable on phones, tablets, foldables, and desktops.
Published on • October 28, 2026
AI Assistant

One of Flutter’s primary goals is to let you develop apps from a single codebase that look and feel great on any platform. That means your app might appear on screens of many different sizes — from a watch, to a foldable phone with two screens, to a high-definition monitor — with input from a physical or virtual keyboard, a mouse, a touchscreen, or any number of other devices.
The Flutter documentation draws an important distinction between two design concepts:
- Responsive design is about fitting the UI into the available space.
- Adaptive design is about the UI being usable in the space.
A responsive app adjusts the placement of design elements to fit the available space. An adaptive app selects the appropriate layout and input devices to be usable in the available space — for example, deciding whether a tablet UI should use bottom navigation or side-panel navigation. Ideally, your app should be both.
MediaQuery: The Foundation of Adaptive Layout
Every adaptive layout decision starts with knowing the environment. Flutter’s MediaQuery gives you a snapshot of the current device: screen size, orientation, pixel density, text scale factor, padding from notches and system bars, and whether a keyboard is visible.
final size = MediaQuery.sizeOf(context);
final orientation = MediaQuery.orientationOf(context);
final textScale = MediaQuery.textScalerOf(context).scale(1.0);
Two important API notes for modern Flutter:
- Use the specific accessors like
MediaQuery.sizeOf(context)rather thanMediaQuery.of(context).size. The...Ofvariants rebuild only widgets that depend on that particular value, avoiding unnecessary rebuilds when unrelated properties change. - For text scaling, prefer
MediaQuery.textScalerOf(context)over the oldertextScaleFactor, which is deprecated.TextScalerhandles non-linear scaling properly for accessibility.
The simplest adaptive branch is a width check:
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
if (width >= 840) {
return const DesktopShell();
} else if (width >= 600) {
return const TabletShell();
}
return const PhoneShell();
}
Material Design’s canonical breakpoints — 600dp for expanded width and 840dp for large screens — are a reasonable starting point, but your content should drive the final numbers.
LayoutBuilder: Constraints, Not Sizes
While MediaQuery tells you about the device, LayoutBuilder tells you about the space your widget actually occupies. This is essential for components embedded in larger layouts — a card in a grid, a panel inside a row — where the available space differs from the full screen.
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 300) {
return const CompactMetric();
}
return const FullMetric();
},
);
The key mental model: Flutter’s layout is constraint-down, size-up. Parents pass constraints down; children report their size up. LayoutBuilder gives you the incoming constraints at any point in the tree, which lets a single reusable widget adapt to wherever it’s placed.
SafeArea and Insets
Notches, display cutouts, rounded corners, and system toolbars all eat into your usable area. Wrapping your scaffold content in SafeArea insets the UI to avoid system intrusions:
SafeArea(
child: Column(
children: [/* content */],
),
);
On large screens and foldables, the story gets more interesting. A foldable in tabletop mode has a hinge in the middle of the display; content shouldn’t render under the hinge. Flutter exposes this information through MediaQuery’s displayFeatures, and SafeArea can be configured to avoid foldable hinges as well as screen edges.
For desktop-style layouts, you’ll often want to avoid over-insetting: a full-screen desktop app doesn’t need padding for a notch that doesn’t exist. Checking MediaQuery.viewInsets and platform-estimated padding lets you apply insets only when they’re real.
Adaptive Navigation: NavigationBar vs NavigationRail
The clearest example of adaptive design is navigation. Material specifies different navigation components for different window sizes:
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
final useRail = width >= 600;
return Scaffold(
appBar: AppBar(title: const Text('Dashboard')),
body: Row(
children: [
if (useRail)
NavigationRail(
selectedIndex: _index,
onDestinationSelected: setState,
labelType: width >= 1024
? NavigationRailLabelType.all
: NavigationRailLabelType.none,
destinations: _destinations,
)
else
const SizedBox.shrink(),
Expanded(child: _pages[_index]),
],
),
bottomNavigationBar: useRail
? null
: NavigationBar(
selectedIndex: _index,
onDestinationSelected: setState,
destinations: _destinations,
),
);
}
This is the “should a tablet use bottom navigation or side-panel navigation?” question answered in code: phones get the bottom bar, tablets and desktops get the rail, and wide desktops even show labels permanently.
VisualDensity and Input Adaptation
Adaptive design isn’t only about size — it’s about input devices. A touch target that’s comfortable on a phone is wastefully large with a mouse, and keyboard navigation must work on desktop.
VisualDensity adjusts spacing based on the platform’s input type:
Theme(
data: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
child: const MyApp(),
);
On platforms with fine pointers (desktop), widgets compact themselves slightly; on touch platforms they keep comfortable spacing. You can also react to the actual pointer device at runtime using Listener and checking event.kind (PointerDeviceKind.touch vs PointerDeviceKind.mouse) to enable hover interactions only when a mouse is present.
For desktop usability, don’t forget:
- Keyboard accelerators —
ShortcutsandActionswidgets map key combinations to intents, giving desktop users the Ctrl/Cmd shortcuts they expect. - Focus management —
FocusTraversalGroupandFocusScopecontrol tab order so keyboard users can navigate without a mouse. - Mouse hover —
MouseRegionchanges the cursor and enables hover states, which touch users never see but desktop users expect.
Best Practices for Adaptive Apps
A few rules of thumb that hold up in production:
- Branch on width, not platform.
Platform.isIOStells you the OS; it tells you nothing about whether the user has your app in split-screen, a foldable half-screen, or a desktop window resized to phone width. Width-based branching handles all of these for free. - Prefer
Expandedand flexible layouts over fixed sizes. Hard-coded pixel widths are the enemy of adaptivity; constraints and fractions scale. - Test at multiple window sizes early. Flutter DevTools lets you preview at arbitrary sizes; for desktop targets, simply resize the window and watch your breakpoints.
- Keep adaptive logic close to the layout. Encapsulate the size branch in its own widget so screens stay declarative:
class AdaptiveShell extends StatelessWidget {
const AdaptiveShell({super.key, required this.child});
final Widget child;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final wide = constraints.maxWidth >= 600;
return Row(
children: [
if (wide) const SideNavigation(),
Expanded(child: child),
],
);
},
);
}
}
Putting It Together
Adaptive layout in Flutter is not a single widget — it’s a mindset supported by a small toolkit: MediaQuery for device facts, LayoutBuilder for local constraints, SafeArea for system intrusions, and platform-appropriate navigation and input components selected by width. Apps that invest in this toolkit once ship the same codebase convincingly to a phone, a tablet, a foldable, and a desktop — which is exactly what Flutter promises.
The Flutter docs’ adaptive and responsive design section goes deeper on large screens, foldables, capabilities and policies, and automatic platform adaptations. Start there, pick your breakpoints, and let the constraints do the work.
Further Reading
- Adaptive and responsive design in Flutter — the official overview covering general approach, SafeArea & MediaQuery, large screens & foldables, user input & accessibility, and best practices.
- Build a layout — Flutter’s layout tutorial if you’re new to the constraint model.
- Understanding constraints — the deep dive on how Flutter’s “constraints down, sizes up” model actually works.