Skip to main content

Responsive and adaptive UI

About 1 min

Responsive and adaptive UI

Flutter apps can run on phones, tablets, desktops and web browsers. A good app does more than stretch: it adapts to the available space and input method.

Responsive vs adaptive

ConceptMeaning
Responsivethe layout fits the available space
Adaptivethe UI chooses controls that make sense for the device

For example, a phone might use a bottom NavigationBar, while a tablet might use a side NavigationRail.

SafeArea

Use SafeArea to avoid notches, system bars and rounded screen corners.

return const SafeArea(
  child: Text("This content stays visible"),
);

LayoutBuilder

LayoutBuilder lets a widget react to the space it receives.

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

  
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth < 600) {
          return const PhoneLayout();
        }

        return const TabletLayout();
      },
    );
  }
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Use a bottom navigation bar on compact screens.

NavigationBar(
  selectedIndex: selectedIndex,
  onDestinationSelected: onDestinationSelected,
  destinations: const [
    NavigationDestination(icon: Icon(Icons.home), label: "Home"),
    NavigationDestination(icon: Icon(Icons.person), label: "Profile"),
  ],
)

Use a side navigation rail when there is more horizontal space.

NavigationRail(
  selectedIndex: selectedIndex,
  onDestinationSelected: onDestinationSelected,
  destinations: const [
    NavigationRailDestination(icon: Icon(Icons.home), label: Text("Home")),
    NavigationRailDestination(icon: Icon(Icons.person), label: Text("Profile")),
  ],
)

Adaptive scaffold example

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

  
  State<AdaptiveHomePage> createState() => _AdaptiveHomePageState();
}

class _AdaptiveHomePageState extends State<AdaptiveHomePage> {
  int selectedIndex = 0;

  
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final bool isCompact = constraints.maxWidth < 600;

        if (isCompact) {
          return Scaffold(
            body: const SafeArea(child: PageContent()),
            bottomNavigationBar: NavigationBar(
              selectedIndex: selectedIndex,
              onDestinationSelected: (index) {
                setState(() {
                  selectedIndex = index;
                });
              },
              destinations: const [
                NavigationDestination(icon: Icon(Icons.home), label: "Home"),
                NavigationDestination(icon: Icon(Icons.map), label: "Trips"),
              ],
            ),
          );
        }

        return Scaffold(
          body: SafeArea(
            child: Row(
              children: [
                NavigationRail(
                  selectedIndex: selectedIndex,
                  onDestinationSelected: (index) {
                    setState(() {
                      selectedIndex = index;
                    });
                  },
                  destinations: const [
                    NavigationRailDestination(
                      icon: Icon(Icons.home),
                      label: Text("Home"),
                    ),
                    NavigationRailDestination(
                      icon: Icon(Icons.map),
                      label: Text("Trips"),
                    ),
                  ],
                ),
                const VerticalDivider(width: 1),
                const Expanded(child: PageContent()),
              ],
            ),
          ),
        );
      },
    );
  }
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 





Best practices

  • Test on small phones, large phones, tablets and web.
  • Do not use fixed widths for full pages.
  • Use Expanded, Flexible, Wrap and LayoutBuilder.
  • Keep touch targets large enough.
  • Check that text still fits when the system font size is increased.

Exercise

Make the Travel Notes app adaptive:

  • show the list and detail page separately on phones
  • show the list and selected detail side by side on tablets
  • use NavigationRail when the screen is wide enough