Adaptive layout
Enforces adapting layout by available CONSTRAINTS/size, never device or platform checks — LayoutBuilder + MediaQuery.sizeOf/paddingOf/viewInsetsOf (not .of) for narrow rebuilds, Material 3 window size classes (compact <600, medium 600-840, expanded 840-1200, large >1200) as the breakpoint vocabulary, navigation affordance chosen by width (NavigationBar → NavigationRail → NavigationDrawer), list-detail single-pane-vs-two-pane, readable max-width via ConstrainedBox, Flexible/Expanded/FractionallySizedBox over fixed widths, SafeArea + display cutouts + keyboard insets, never lock orientation, foldable/hinge awareness via MediaQuery.displayFeatures, and golden-matrix verification across sizes. Use when building responsive or adaptive UI, tablet/desktop/foldable support, master-detail or two-pane screens, a NavigationRail-vs-BottomNav shell, breakpoints, LayoutBuilder, MediaQuery sizing, SafeArea/cutouts, or fixing overflow at large widths.From its SKILL.md
npx -y skills add zakariaf/Flutter-Skills --skill adaptive-layoutAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
10.8 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it
Adaptive Layout
Adapt to the space you are given, never to the device you think you are on. A phone in a foldable's front display, a resized desktop window, and a tablet in split-screen all defeat Platform.isX checks — but they all report their real constraints. Branch on width, not hardware.
Read the reference for the task at hand:
references/window-size-classes.md— the Material 3 window size-class breakpoints as a shared vocabulary, theWindowSizeClassenum pattern,MediaQuery.sizeOf/paddingOf/viewInsetsOfvs.of, readable max-width, SafeArea and display cutouts, keyboard insets, orientation, foldable hinge awareness.references/list-detail-and-navigation.md— single-pane-navigate vs side-by-side two-pane, choosing the navigation affordance by width and coordinating with the go_router shell, keeping selection state in a Notifier so both panes agree.
Run scripts/check_adaptive.sh before a PR.
Non-negotiable rules
-
Adapt by constraints/size, never by device or platform. No
Platform.isAndroid/Platform.isIOS/kIsWebto pick a layout. UseLayoutBuilder(local box constraints) orMediaQuery.sizeOf(context)(window size). WHY: a resized window, split-screen, and foldable all break device checks; constraints are always true. -
Use the Material 3 window size classes as the breakpoint vocabulary. Compact
<600, medium600–840, expanded840–1200, large1200–1600, extra-large≥1600(logical px width). WHY: these are STANDARD structural breakpoints (not design tokens); one shared enum keeps every screen's breakpoints identical. -
Read the narrowest MediaQuery aspect:
sizeOf/paddingOf/viewInsetsOf/viewPaddingOf, notMediaQuery.of(context). WHY:.ofsubscribes the widget to EVERY MediaQuery change (keyboard, rotation, text scale); the aspect getters rebuild only when that one field changes — seeflutter-performance. -
Pick the navigation affordance by width, not by a per-screen guess.
NavigationBar(compact) →NavigationRail(medium/expanded) →NavigationDrawer(large). Decide once in the shell. WHY: mixing affordances across screens disorients; one width→affordance map is consistent by construction. Coordinate withnavigation-and-routing'sStatefulShellRoute. -
List-detail collapses to one pane on compact and splits on expanded. Compact: the list navigates to a detail route. Expanded: list and detail sit side-by-side; selection is shared state. WHY: two panes on a phone are unreadable; one pane on a desktop wastes 70% of the width.
-
Never lock orientation (
SystemChrome.setPreferredOrientationsto force portrait). WHY: it breaks tablets, foldables, and accessibility mounts. Let the size classes handle both orientations. -
Constrain readable content with a max width; use
Flexible/Expanded/FractionallySizedBox, not fixed pixel widths, for top-level regions. WHY: full-width body text on a wide screen is unreadable (~40–75 chars/line is legible); fixed region widths overflow or leave dead space. -
Wrap edge content in
SafeAreaand respect display cutouts + keyboard insets. Read cutouts/system bars viaMediaQuery.paddingOf/viewPaddingOf; the keyboard viaMediaQuery.viewInsetsOf. WHY: notches, punch-holes, and the on-screen keyboard occlude content that ignores insets. -
Never assume a fixed cell/row height. Large text scale can double a cell's height; let content size itself. WHY: a hardcoded
SizedBox(height: 48)clips at 200% text scale — seeaccessibility-as-codeandwidget-composition. -
Verify across sizes with a golden matrix, not one device. Snapshot each adaptive screen at compact/medium/expanded (+ largest text, LTR/RTL). WHY: adaptive bugs only appear at the breakpoint you did not open — see
widget-golden-and-a11y-testing.
Size classes as one shared enum
Define the breakpoints once; every screen reads the same map.
enum WindowSizeClass { compact, medium, expanded, large, extraLarge }
WindowSizeClass windowSizeClassFor(double width) => switch (width) {
< 600 => WindowSizeClass.compact,
< 840 => WindowSizeClass.medium,
< 1200 => WindowSizeClass.expanded,
< 1600 => WindowSizeClass.large,
_ => WindowSizeClass.extraLarge,
};
// In a widget: rebuilds only when the window SIZE changes.
final sizeClass = windowSizeClassFor(MediaQuery.sizeOf(context).width);
Prefer LayoutBuilder when the decision depends on the LOCAL box (a widget inside a split view, a card grid), and MediaQuery.sizeOf when it depends on the whole window (the top-level shell).
// Local constraints drive a card grid's column count.
LayoutBuilder(
builder: (context, constraints) {
final columns = constraints.maxWidth ~/ 280; // min card width, structural
return GridView.count(crossAxisCount: columns.clamp(1, 4), children: cards);
},
);
Readable width and flexible regions
// Center and cap body text to a legible measure; never full-bleed on desktop.
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 720), // structural readable cap
child: article,
),
);
// Split a wide body proportionally, not with magic fixed widths.
Row(
children: [
Expanded(flex: 2, child: primaryPane),
Expanded(flex: 3, child: secondaryPane),
],
);
Insets, cutouts, keyboard, foldables
// Keyboard height: rebuilds only when the keyboard shows/hides.
final keyboard = MediaQuery.viewInsetsOf(context).bottom;
// Notch/system-bar padding without subscribing to text-scale/rotation changes.
final safeTop = MediaQuery.paddingOf(context).top;
// Foldable: a vertical fold/hinge spanning the full height => split around it.
final seam = MediaQuery.displayFeaturesOf(context)
.where((f) =>
(f.type == DisplayFeatureType.fold ||
f.type == DisplayFeatureType.hinge) &&
f.bounds.top == 0 && // starts at the top edge...
f.bounds.width < f.bounds.height) // ...taller than wide => vertical
.firstOrNull; // firstOrNull: import 'package:collection/collection.dart';
Wrap the outermost interactive region in SafeArea; opt specific edges out (bottom: false) when a NavigationBar or scrolling body should reach the edge.
Navigation affordance by width
// One place decides the shell chrome; screens stay affordance-agnostic.
Widget shellFor(WindowSizeClass sc, {required Widget body, required int index}) {
return switch (sc) {
WindowSizeClass.compact =>
Scaffold(body: body, bottomNavigationBar: _NavBar(index: index)),
WindowSizeClass.medium || WindowSizeClass.expanded =>
Scaffold(body: Row(children: [_Rail(index: index), Expanded(child: body)])),
WindowSizeClass.large || WindowSizeClass.extraLarge =>
Scaffold(body: Row(children: [_Drawer(index: index), Expanded(child: body)])),
};
}
See examples/adaptive_scaffold.dart for the full shell and examples/list_detail_pane.dart for the two-pane pattern.
Anti-patterns
if (Platform.isIOS) ... else ...to choose a layout. Branch onMediaQuery.sizeOf(context).width.MediaQuery.of(context).size.width— subscribes to every MediaQuery change. UseMediaQuery.sizeOf(context).SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])to dodge landscape layout. Support both.Container(width: 375)orSizedBox(width: 320)sizing a top-level pane. UseExpanded/Flexible/FractionallySizedBox/ConstrainedBox.- A
bool isTablet = MediaQuery.of(context).size.shortestSide > 600flag threaded everywhere. Compute aWindowSizeClassfrom width at the shell. - Two panes shown on compact width, forcing horizontal scroll. Collapse to one pane and navigate.
- Full-width paragraphs on desktop with no max-width. Cap the measure.
- Assuming the keyboard is hidden — content jumps under it. Pad by
MediaQuery.viewInsetsOf(context).bottom.
Definition of done
- No
Platform.is*/kIsWeb/dart:ioused to select a layout (platform-specific plugins are fine). - Breakpoints come from the shared
WindowSizeClasshelper, not ad-hoc numbers scattered per screen. - MediaQuery is read via
sizeOf/paddingOf/viewInsetsOf/viewPaddingOf, not.of. - Navigation affordance is chosen by width in ONE place (the shell), coordinated with
navigation-and-routing. - List-detail screens collapse to one pane on compact and split on expanded, sharing selection state.
- No fixed pixel widths on top-level regions; readable content is max-width capped.
- Orientation is not locked; edge content uses
SafeArea; keyboard insets are respected. - Golden matrix covers compact/medium/expanded (+ largest text, RTL).
scripts/check_adaptive.shpasses.
Related skills
widget-composition— structural layout primitives, computed sizing, edge-to-edge vs SafeArea, never-fixed cell heights.navigation-and-routing— theStatefulShellRoutethis shell renders; width picks the chrome, routes pick the branch.flutter-performance— whysizeOf/selectbeat.offor rebuild scope.accessibility-as-code— large text scale interacts with every adaptive cell.widget-golden-and-a11y-testing— the device × text-scale × RTL golden matrix that proves adaptivity.state-management-riverpod— where shared list-detail selection lives.
References
- Material 3 layout / window size classes: https://m3.material.io/foundations/layout/applying-layout/window-size-classes
- Adaptive & responsive design (Flutter): https://docs.flutter.dev/ui/adaptive-responsive
MediaQueryAPI (aspect getters): https://api.flutter.dev/flutter/widgets/MediaQuery-class.htmlLayoutBuilder: https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.htmlNavigationRail: https://api.flutter.dev/flutter/material/NavigationRail-class.htmlDisplayFeature/ foldables: https://api.flutter.dev/flutter/dart-ui/DisplayFeature-class.html
What ships with it: 5 files
28.2 KB alongside SKILL.md, 1 of them executable
examples/
- adaptive_scaffold.dart5.5 KB
- list_detail_pane.dart5.9 KB
references/
scripts/
- check_adaptive.shruns2.1 KB
Gives 0 of the 12 instructions most ui components skills give in ~2.4k tokens
Counted across 293 of the 305 authors here whose files we hold, read 2026-09-06
- Start with a --design-system search before designingin 17 of 293, across 15 files
- Ensure 4.5:1 minimum text contrastin 16 of 293, across 15 files
- Verify the pre-delivery checklist before delivering UI codein 16 of 293, across 14 files
- Use SVG icons instead of emojisin 15 of 293, across 14 files
- Keep touch targets at least 44 pointsin 14 of 293, across 12 files
- Check page override files before master rulesin 13 of 293, across 11 files
- Supplement with domain searches as neededin 13 of 293, across 11 files
- Add cursor-pointer to all clickable elementsin 12 of 293
- Use CSS variables for themingin 11 of 293, across 7 files
- Use semantic color tokens, not raw hexin 11 of 293, across 8 files
- Meet WCAG AA color contrastin 11 of 293, across 7 files
- Default stack to html-tailwind when unspecifiedin 10 of 293
Said here and by no other author read
- Adapt layout by constraints, never by device or platform
- Use Material 3 window size classes as breakpoints
- Read MediaQuery via sizeOf, paddingOf, viewInsetsOf, not .of
- Pick navigation affordance by width in one shell
- Collapse list-detail on compact, split on expanded
- Cap readable content with a max width
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.