Skip to content
Boring Doc
Official documentation (snapshot)doc

LayoutBuilder and adaptive layouts

Learn how to use the LayoutBuilder widget.

Source last updated Oct 21, 2026 · last synchronized 3 weeks ago · commit b3045439b7c0 · read on docs.flutter.dev

This is a rendered snapshot of the official page as stored by Boring Doc for provenance and search. Site-specific components (embedded demos, DartPad, images with relative paths) may not render; use the official page for the full experience. Content © The Flutter Authors, CC BY 3.0.

Learn how to create layouts that adapt to different screen widths.


Introduction

Modern apps need to work well on screens of all sizes. On this page, you'll learn how to create layouts that adapt to different screen widths. This app shows a sidebar on large screens and a navigation-based UI on small screens. Specifically, this app handles two screen sizes:

  • Large screens (tablets, desktop): Shows contact groups and contact details side-by-side.
  • Small screens (phones): Uses navigation to move between contact groups and details.

Create the contact groups page

First, create the basic structure of the ContactGroupsPage widget for your contact groups screen. Create lib/screens/contact_groups.dart and add the following basic structure:

import 'package:cupertino_ui/cupertino_ui.dart';

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

  @override
  Widget build(BuildContext context) {
    return const CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: Center(child: Text('Contact Groups will go here')),
    );
  }
}

Create the contacts page

Similarly, create lib/screens/contacts.dart to eventually display individual contacts:

import 'package:cupertino_ui/cupertino_ui.dart';

class ContactListsPage extends StatelessWidget {
  const ContactListsPage({super.key, required this.listId});

  final int listId;

  @override
  Widget build(BuildContext context) {
    return const CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: Center(child: Text('Lists of contacts will go here')),
    );
  }
}

The ContactListsPage widget and ContactGroupsPage widget are placeholder pages that are needed to implement the adaptive layout widget, which you'll do next.

Build the adaptive layout foundation

Create lib/screens/adaptive_layout.dart and start with the following basic structure:

import 'package:cupertino_ui/cupertino_ui.dart';

import 'contact_groups.dart';

class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});

  @override
  State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}

class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  @override
  Widget build(BuildContext context) {
    return const ContactGroupsPage(); // Temporary placeholder
  }
}

This is a StatefulWidget because the adaptive layout eventually manages which contact group is currently selected.

Next, add the screen size detection logic to lib/screens/adaptive_layout.dart:

import 'package:cupertino_ui/cupertino_ui.dart';

import 'contact_groups.dart';

const largeScreenMinWidth = 600;

class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});

  @override
  State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}

class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;

        if (isLargeScreen) {
          return const Text('Large screen layout'); // Temporary
        } else {
          return const ContactGroupsPage();
        }
      },
    );
  }
}

The LayoutBuilder widget provides information about the parent's size constraints. In the builder callback, you receive aBoxConstraints object that tells you the maximum available width and height.

By checking if constraints.maxWidth > largeScreenMinWidth, you can decide which layout to show. The 600-pixel threshold is a common breakpoint that separates phone-sized screens from tablet-sized screens.

Update the main app

Update main.dart to use the adaptive layout, so you can see your changes:

import 'package:cupertino_ui/cupertino_ui.dart';

import 'data/contact_group.dart';
import 'screens/adaptive_layout.dart';

final contactGroupsModel = ContactGroupsModel();

void main() {
  runApp(const RolodexApp());
}

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

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      title: 'Rolodex',
      theme: CupertinoThemeData(
        barBackgroundColor: CupertinoDynamicColor.withBrightness(
          color: Color(0xFFF9F9F9),
          darkColor: Color(0xFF1D1D1D),
        ),
      ),
      home: AdaptiveLayout(),
    );
  }
}

If you're running in Chrome, you can resize the browser window to see layout changes.

Add list selection functionality

The large screen layout needs to track which contact group is selected. Update the state object in lib/screens/adaptive_layout.dart with the following code:


import 'package:cupertino_ui/cupertino_ui.dart';

import 'contact_groups.dart';

const largeScreenMinWidth = 600;

class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});

  @override
  State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}

class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  int selectedListId = 0;

  void _onContactListSelected(int listId) {
    setState(() {
      selectedListId = listId;
    });
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;

        if (isLargeScreen) {
          return const Text('Large screen layout');
        } else {
          return const ContactGroupsPage();
        }
      },
    );
  }
}

The selectedListId variable tracks the currently selected contact group, and _onContactListSelected updates this value when the user makes a choice.

Build the large screen layout

Now, implement the side-by-side layout for large screens in lib/screens/adaptive_layout.dart. First, replace the temporary text with a widget that contains the proper layout.


import 'package:cupertino_ui/cupertino_ui.dart';

import 'contact_groups.dart';

const largeScreenMinWidth = 600;

class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});

  @override
  State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}

class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  int selectedListId = 0;

  void _onContactListSelected(int listId) {
    setState(() {
      selectedListId = listId;
    });
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;

        if (isLargeScreen) {
          return _buildLargeScreenLayout();
        } else {
          // For small screens, use the original, navigation-style approach.
          return const ContactGroupsPage();
        }
      },
    );
  }

  Widget _buildLargeScreenLayout() {
    return const CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: SafeArea(child: Row(children: [Text('Sidebar'), Text('Details')])),
    );
  }
}

The large screen layout uses a Row to place the sidebar and details side-by-side. SafeArea ensures that the content doesn't overlap with system UI elements like the status bar.

Now, set the sizes of the two panels and add a visual divider in lib/screens/adaptive_layout.dart:

Widget _buildLargeScreenLayout() {
  return CupertinoPageScaffold(
    backgroundColor: CupertinoColors.extraLightBackgroundGray,
    child: SafeArea(
      child: Row(
        children: [
          const SizedBox(width: 320, child: Text('Sidebar placeholder')),
          Container(width: 1, color: CupertinoColors.separator),
          const Expanded(child: Text('Details placeholder')),
        ],
      ),
    ),
  );
}

This layout creates the following:

  • A fixed-width sidebar (320 pixels) for contact groups.
  • A 1-pixel divider between the panels.
  • A details panel that uses an Expanded widget to take the remaining space.

Test the adaptive layout

Hot reload your app and test the responsive behavior. If you're running in Chrome, you can resize the browser window to see the layout change:

  • Wide window (> 600px): Shows placeholder text for the sidebar and details side-by-side.
  • Narrow window (< 600px): Shows only the contact groups page.

Both the sidebar and main content area show placeholder text for now.

In the next lesson, you'll implement slivers to fill in the contact list content.

Review

Test yourself

Was this useful?

Content from flutter/website, © The Flutter Authors, used under the Creative Commons Attribution 3.0 License; code samples under the BSD License.