Skip to main content

Command Palette

Search for a command to run...

LayoutBuilder vs MediaQuery in Flutter — which one should you use?

Updated
5 min read
F
Thinking...

Responsive UIs feel simple until a widget misbehaves on a tablet or inside a constrained parent. Pick the wrong tool and your layout either ignores available space or leaps to global assumptions. This post explains what LayoutBuilder and MediaQuery do, how they work, and when to use each so your UI adapts correctly.

Quick definitions

  • LayoutBuilder — a widget that gives you the parent's BoxConstraints during build. Use it when you care about how much space the parent actually gives a child.

  • MediaQuery — an inherited widget that exposes device and window metrics (screen size, device pixel ratio, safe-area insets, text scale factor). Use it when you need global device information.

Analogy: LayoutBuilder is like measuring the shelf where you want to place a book (available space). MediaQuery is like knowing the room’s dimensions (overall space and padding). Summary: LayoutBuilder = parent constraints; MediaQuery = global device/window metrics.

How they work (internals, in plain words)

Flutter's layout uses constraints that flow down the tree and sizes that flow up. Parent widgets give BoxConstraints to children. LayoutBuilder taps directly into that constraint pipeline and calls its builder whenever the constraints change.

MediaQuery is an InheritedWidget that the framework populates (via WidgetsApp/MaterialApp) with window metrics. MediaQuery.of(context) looks up the nearest MediaQuery above the context and returns a MediaQueryData object (size, padding, devicePixelRatio, textScaleFactor, orientation). MediaQuery updates when platform/window metrics change.

One-line summary: LayoutBuilder listens to parent constraints; MediaQuery provides global window/device metrics.

Practical differences that matter

  • Use LayoutBuilder when the decision depends on the widget's available space (constraints.maxWidth / maxHeight).

  • Use MediaQuery when the decision depends on the screen or window metrics (safe-area insets, text scale, device pixel ratio, total screen size).

  • MediaQuery does NOT reflect the size a parent reserved for your widget; it reports the window. LayoutBuilder does.

One-line summary: LayoutBuilder = local constraints; MediaQuery = global metrics.

Examples

LayoutBuilder — switch between column/row based on parent width:

Widget build(BuildContext context) {
  return LayoutBuilder(
    builder: (context, constraints) {
      if (constraints.maxWidth > 600) {
        return Row(children: [Expanded(child: Sidebar()), Expanded(child: Content())]);
      }
      return Column(children: [Content(), Sidebar()]);
    },
  );
}

This lets the widget adapt to the space the parent actually provides. Summary: use LayoutBuilder for component-level breakpoints.

MediaQuery — read safe area and screen size:

final mq = MediaQuery.of(context);
final screenSize = mq.size;
final horizontalPadding = mq.padding.horizontal;
final textScale = mq.textScaleFactor;

// use full-screen background minus safe area
Container(
  width: screenSize.width,
  padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
  child: Text('Hello', textScaleFactor: textScale),
);

Summary: use MediaQuery for device/window-level information like insets and text scaling.

Combine both — get parent width and account for safe-area:

LayoutBuilder(
  builder: (context, constraints) {
    final safeHorizontal = MediaQuery.of(context).padding.horizontal;
    final available = constraints.maxWidth - safeHorizontal;
    // build using 'available'
    return SizedBox(width: available, child: MyResponsiveChild());
  },
)

Summary: you can mix them: LayoutBuilder for local size, MediaQuery for window insets/scale.

Common pitfalls and gotchas

  • Measuring screen size via MediaQuery inside a constrained parent leads to wrong assumptions. For example, a widget in a split view using MediaQuery.size.width might think it has full-screen width and overflow. Use LayoutBuilder there.

  • LayoutBuilder's builder runs any time parent constraints change. Avoid heavy computations or creating big widget trees inside the builder; keep it lightweight. Use const widgets or extract expensive parts.

  • MediaQuery.of(context) requires a context that has a MediaQuery above it (MaterialApp/WidgetsApp normally adds one). Calling it too early in the tree (before MaterialApp) fails.

  • Orientation: you can use MediaQuery.of(context).orientation or OrientationBuilder. OrientationBuilder rebuilds when orientation-related constraints change; MediaQuery orientation reads the same value from MediaQueryData.

  • Text scaling: honor MediaQuery.textScaleFactor for accessibility. Hard-coded font sizes can break when users increase text size.

One-line summary: prefer LayoutBuilder for layout logic tied to parent size, and watch rebuild/performance trade-offs.

Performance considerations

  • MediaQuery.of(context) is cheap. It just reads inherited data.

  • LayoutBuilder triggers rebuilds when constraints change. That’s usually fine, but avoid expensive work inside the builder. Extract large subtrees to children that depend on stable inputs.

  • Use const constructors and smaller widgets to limit rebuild cost.

One-line summary: MediaQuery is lightweight; treat LayoutBuilder builds as potentially frequent and keep them efficient.

Rules of thumb (cheat sheet)

  • If your widget needs to know "how much space did the parent give me?" → use LayoutBuilder.

  • If your widget needs device/window metrics (safe-area, text scale, device pixel ratio, overall screen size) → use MediaQuery.

  • For component breakpoints (cards, nav bars inside containers) → prefer LayoutBuilder.

  • For app-level breakpoints (mobile vs tablet entire screen) → MediaQuery.size can be okay, but consider using LayoutBuilder at the top-level scaffold if your scaffold can be constrained.

  • Combine both when you need both parent constraints and window insets/scale.

One-line summary: choose based on whether the metric is local (constraints) or global (window/device).

Quick checklist before you ship

  • Test your layout inside constrained parents (dialogs, split views, nested columns).

  • Verify behavior with different textScaleFactor values for accessibility.

  • Check safe-area insets on notched devices and when keyboard appears.

  • Profile rebuilds if you use many LayoutBuilders.

One-line summary: test under realistic constraints and accessibility settings.

Conclusion and next step

Use LayoutBuilder when layout decisions depend on available parent space; use MediaQuery for device/window-level info like insets and text scale. When in doubt, ask: "Is this about the parent’s shelf or the room’s dimensions?" and pick LayoutBuilder for the shelf and MediaQuery for the room.

Try a small exercise: pick a widget that currently uses MediaQuery.size.width to decide layout, replace it with LayoutBuilder, and run the app inside a split view or a dialog. Notice how the behavior changes and stabilize the builder’s workload.