adaptive_scaffold_plus 1.1.1
adaptive_scaffold_plus: ^1.1.1 copied to clipboard
A production-ready replacement for flutter_adaptive_scaffold. Adapts navigation (BottomNavigationBar, NavigationRail, Drawer) per Material 3 guidelines.
adaptive_scaffold_plus #
A production-ready, community-maintained replacement for the discontinued
flutter_adaptive_scaffold package. Drop it into any Flutter app to get
navigation that automatically adapts to screen size, following Material 3
guidelines — with zero manual breakpoint logic.
| Screen width | Navigation shown |
|---|---|
< 600px |
Bottom NavigationBar (docked) or a floating, frosted-glass pill bar |
600–1200px |
NavigationRail (side) |
> 1200px |
Permanent NavigationDrawer (full) |
Resize the window / rotate the device and the transition animates automatically.
Install #
dependencies:
adaptive_scaffold_plus: ^1.0.5
import 'package:adaptive_scaffold_plus/adaptive_scaffold_plus.dart';
Quick start #
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: AdaptiveScaffoldPlus(
destinations: const [
AdaptiveDestination(icon: Icons.home_outlined, selectedIcon: Icons.home, label: 'Home'),
AdaptiveDestination(icon: Icons.search_outlined, selectedIcon: Icons.search, label: 'Search'),
AdaptiveDestination(icon: Icons.person_outline, selectedIcon: Icons.person, label: 'Profile'),
],
body: (index) => pages[index],
),
);
}
}
That's it — AdaptiveScaffoldPlus picks the right navigation widget for the
current screen size and keeps body(selectedIndex) in sync with whichever
destination is selected.
Floating, frosted-glass navigation bar #
By default the small-screen layout uses a standard, opaque NavigationBar
docked to the bottom edge (AdaptiveNavigationStyle.docked). Set
navigationStyle: AdaptiveNavigationStyle.floating to switch to a
pill-shaped, translucent bar that floats above your content with blurred
background — the modern look used by apps like Instagram.
AdaptiveScaffoldPlus(
destinations: destinations,
navigationStyle: AdaptiveNavigationStyle.floating,
body: (index) => pages[index],
)
The bar automatically adapts its icon color to stay legible over both light and dark content, and shows an animated pill highlight behind the selected icon.
Tuning the floating bar #
AdaptiveScaffoldPlus(
destinations: destinations,
navigationStyle: AdaptiveNavigationStyle.floating,
navigationBackgroundColor: Colors.black, // tint of the glass surface
floatingNavigationMargin: const EdgeInsets.fromLTRB(16, 0, 16, 16),
floatingNavigationBlur: 30, // frosted-glass blur strength
floatingNavigationOpacity: 0.45, // 0 = fully see-through, 1 = opaque
floatingNavigationHeight: 56,
body: (index) => pages[index],
)
| Parameter | Default | Effect |
|---|---|---|
navigationBackgroundColor |
white (light theme) / black (dark) | Tint color of the glass surface |
floatingNavigationMargin |
EdgeInsets.fromLTRB(16, 0, 16, 16) |
Space between the bar and the screen edges |
floatingNavigationBlur |
30 |
BackdropFilter blur sigma (frosted-glass strength) |
floatingNavigationOpacity |
0.45 |
Alpha of the tint color, 0–1 |
floatingNavigationHeight |
56 |
Height of the bar itself |
You can also use the bar standalone, outside of AdaptiveScaffoldPlus:
Scaffold(
extendBody: true, // lets content scroll behind the floating bar
body: MyContent(),
bottomNavigationBar: SafeArea(
minimum: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: FloatingNavigationBar(
destinations: destinations,
selectedIndex: selectedIndex,
onDestinationSelected: (i) => setState(() => selectedIndex = i),
),
),
)
Destinations #
Each nav item is an AdaptiveDestination — it maps to a NavigationBar /
FloatingNavigationBar item on small screens, a NavigationRail item on
medium screens, and a NavigationDrawer item on large screens.
AdaptiveDestination(
icon: Icons.favorite_outline,
selectedIcon: Icons.favorite, // shown when selected; falls back to `icon`
label: 'Favourites',
tooltip: 'Your favourites', // shown on long-press / hover
badge: Text('3'), // optional badge/notification content
)
At least 2 destinations are required.
Full AdaptiveScaffoldPlus reference #
AdaptiveScaffoldPlus(
// Required
destinations: [...], // List<AdaptiveDestination>, min 2
body: (index) => pages[index], // Widget Function(int selectedIndex)
// Layout
secondaryBody: (index) => detail[index],// optional second panel (list-detail pattern, large screens)
appBar: AppBar(title: const Text('My App')),
floatingActionButton: FloatingActionButton(onPressed: () {}, child: const Icon(Icons.add)),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
backgroundColor: Colors.white,
resizeToAvoidBottomInset: true,
// Selection
initialIndex: 0,
onDestinationSelected: (index) => print('selected $index'),
// Breakpoints
breakpoints: const AdaptiveBreakpoints(small: 600, large: 1200),
// Medium screen (NavigationRail)
railLabelType: NavigationRailLabelType.all,
extendedRail: false,
// Medium/large screen extras
navigationHeader: const Text('MY APP'), // shown above destinations
navigationFooter: const Text('v1.0.5'), // shown below destinations
showNavigationDivider: true,
// Styling shared across nav types
navigationBackgroundColor: Colors.white,
transitionDuration: const Duration(milliseconds: 200),
// Small-screen navigation style (see "Floating" section above)
navigationStyle: AdaptiveNavigationStyle.docked, // or .floating
floatingNavigationMargin: const EdgeInsets.fromLTRB(16, 0, 16, 16),
floatingNavigationBlur: 30,
floatingNavigationOpacity: 0.45,
floatingNavigationHeight: 56,
)
Custom breakpoints #
AdaptiveScaffoldPlus(
breakpoints: const AdaptiveBreakpoints(small: 500, large: 1100),
destinations: [...],
body: (index) => pages[index],
)
You can also check the current size manually anywhere in your widget tree:
final size = Breakpoints.of(context); // ScreenSize.small / .medium / .large
if (Breakpoints.isSmall(context)) { ... }
List-detail pattern (large screens) #
AdaptiveScaffoldPlus(
destinations: [...],
body: (index) => MyListView(),
secondaryBody: (index) => MyDetailView(), // shown beside body only on large screens
)
Low-level layout: AdaptiveLayout + SlotLayout #
For full control over what renders in each screen region (instead of the
opinionated nav-bar/rail/drawer behavior of AdaptiveScaffoldPlus), compose
AdaptiveLayout directly with per-breakpoint SlotLayouts:
AdaptiveLayout(
body: SlotLayout(
config: {
ScreenSize.small: SlotLayoutConfig.from(child: MobilePage()),
ScreenSize.large: SlotLayoutConfig.from(child: DesktopPage()),
},
),
primaryNavigation: SlotLayout(
config: {
ScreenSize.medium: SlotLayoutConfig.from(child: MyNavRail()),
ScreenSize.large: SlotLayoutConfig.from(child: MyDrawer()),
},
),
topNavigation: SlotLayout(
config: {
ScreenSize.small: SlotLayoutConfig.from(child: AppBar(title: const Text('App'))),
},
),
)
Slots: body, secondaryBody, primaryNavigation, secondaryNavigation,
topNavigation, bottomNavigation — each is an optional SlotLayout that
picks a child per ScreenSize, with a fade transition by default
(override via SlotLayoutConfig.from(inAnimation: ..., outAnimation: ...)).
Try it locally #
A runnable demo lives in example/lib/main.dart:
cd example
flutter run -d chrome # or any connected device/desktop target
Resize the window (or use responsive mode in Chrome DevTools) to see the layout switch between bottom bar, rail, and drawer live.
License #
See LICENSE.