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
| Concept | Meaning |
|---|---|
| Responsive | the layout fits the available space |
| Adaptive | the 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();
},
);
}
}
Â
Â
Â
Â
Â
Â
Â
Â
Â
Â
Â
Â
Â
Â
Â
Â
NavigationBar and NavigationRail
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,WrapandLayoutBuilder. - 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
NavigationRailwhen the screen is wide enough