Skip to content
Tech Stack1 October 2026 · 12 min read

Flutter Layouts for Tablets, Foldables, and Desktop

Two of my own FlutterFlow templates fit a 1024 px tablet perfectly and still fail: one crops its alert copy, the other wastes the screen. Here is how to adapt Flutter layouts instead.

Flutter Layouts for Tablets, Foldables, and Desktop

A stretched phone layout is responsive in the narrowest sense and unusable in every sense that matters. It fits. Every pixel of the window is covered, nothing overflows, no yellow-and-black stripes in the corner. And the thing the screen was supposed to tell you is gone, cropped out of a bitmap, or pushed four hundred pixels below where your eye starts.

Two of my own FlutterFlow UI templates were captured at 393 px and at 1024 px on 2026-09-29, and both fail in exactly that way. Neither has a layout bug you could file. Both are broken.

What does adaptive actually mean in Flutter?

Responsive means the content fits the space; adaptive means the interface is usable in that space. Flutter's own documentation draws the line cleanly: "responsive design is about fitting the UI into the space and adaptive design is about the UI being usable in the space" (Adaptive and responsive design in Flutter). The same page adds the question that actually decides your architecture: should a tablet UI use bottom navigation or side-panel navigation?

That distinction sounds academic until you measure a real screen. A responsive failure throws an overflow error and you fix it in ten minutes. An adaptive failure ships, passes review, and quietly costs you the first impression on every tablet your app runs on, because nothing anywhere reports an error.

Two layouts that fit and still fail

Two panels comparing the same alert banner at two window widths: at 393 px the subject line, two detail lines and the date all sit inside the frame, while at 1024 px the banner is wider and shorter and dashed crop lines show the subject line and the date falling outside the visible slice.

Both templates below are mine, both were captured from FlutterFlow Preview on 2026-09-29 at 393x852 and at 1024x1366, and the defects are in the rendered output rather than in anyone's description of it.

The first is Community UI App, a residential community dashboard. Its alert banner is a single raster image with the entire message baked into the artwork. In portrait you read a subject line, two lines of detail, and a date underneath. At 1024 px the banner keeps roughly its height while the width more than doubles, the cover crop eats the top and bottom bands of the artwork, and what survives is a sentence fragment with no subject and no date. The alert still renders. It just no longer says what is happening or when.

Directly below it, the event card's photo goes from roughly 2:1 in portrait to something near 7:1 at tablet width. A letterbox slot where a photograph used to be.

The second is Skillz, a learning dashboard. Nothing crops here. The problem is the opposite: in portrait the greeting lands about 160 logical pixels down and the first course cards sit inside the first viewport, while at 1024 px the greeting falls around 400 pixels down and the search field drifts to roughly 40% of a much taller screen. The column also runs the full 1024 px wide, so a search field designed for a thumb now spans an entire tablet. Flutter's best-practices page has a heading for this exact thing, and it is blunt: don't gobble up all horizontal space (Flutter adaptive design best practices).

Two templates, two opposite symptoms, one cause. Each was composed once, for one width, and then asked to survive arithmetic.

Everything I describe as a correction below is a proposed redesign rather than shipped behavior. The defects are verified from Preview captures; no interaction was runtime-tested in either project, so nothing here claims that search, navigation, attendance or persistence works.

LayoutBuilder or MediaQuery?

Use MediaQuery.sizeOf when the decision belongs to the whole app window, and LayoutBuilder when it belongs to the box your widget was handed. Flutter's guidance is that LayoutBuilder "provides the layout constraints from the parent Widget," so you get sizing for the specific spot in the tree where you placed it, while MediaQuery.sizeOf reports the app window itself (Flutter adaptive design, general approach).

The failure mode is using the window to lay out something that does not own the window.

MediaQuery.sizeOfLayoutBuilder
Measuresthe app windowthe constraints at that point in the tree
Rebuilds whenwindow metrics changethe parent passes different constraints
Right forapp shell, navigation, page-level branchinga card, a pane, a list tile inside a split view
Wrong whenthe widget sits inside a narrow pane of a wide windowyou need the main-axis extent inside a scrollable

That last cell is the edge nobody warns you about until it bites. LayoutBuilder runs during layout and is for the case where "the parent constrains the child's size and doesn't depend on the child's intrinsic size" (LayoutBuilder API). Inside a vertical scroll view the cross axis is constrained and the main axis is not, so a height-based breakpoint there reads infinity and does something stupid. In a sliver context the counterpart is SliverLayoutBuilder. Reach for that instead of fighting the constraints you were given.

Breakpoints come from the window, not the device

A horizontal window-width axis in dp split into compact, medium, expanded, large and extra-large bands, with a marker at 600 dp where a bottom navigation bar gives way to a navigation rail, and the foldable, 8-inch tablet, 10.5-inch tablet and 13-inch Chromebook test configurations plotted along it.

Branch on available width, never on device type. Flutter states it directly: "your choice shouldn't depend on the type of device, but on the device's available window size," and the best-practices page lists if (isTablet) as something to avoid, because window size stopped tracking hardware once ChromeOS resizable windows, tablet multi-window, picture-in-picture, and foldables existed (Flutter adaptive design best practices).

The numbers worth memorising are Android's window size classes, which Material's layout guidance shares. Google's Android window size class reference, current as of 2026, puts the compact class at under 600dp, covering 99.96% of phones in portrait, medium at 600–840dp covering 93.73% of tablets in portrait, and expanded at 840–1200dp covering 97.22% of tablets in landscape, with large and extra-large classes added above that to target desktop and connected displays (Android window size classes).

Width classRangeWindow you usually see it onNavigation I reach for
Compactunder 600 dpphone portrait, small split-screen panebottom navigation bar
Medium600–839 dptablet portrait, unfolded inner displaynavigation rail
Expanded840–1199 dptablet landscape, half a desktop windowrail plus a second pane
Large1200–1599 dplarge tablet, maximised laptop windowrail or permanent drawer
Extra-large1600 dp and updesktop, connected displaypermanent drawer, multi-pane

Here's how that went on a real build.

BookBed runs from phones to desktop platforms from one Flutter codebase, but the booking calendar could not simply stretch. I fixed the timeline cells at deliberate interaction dimensions so drag math stayed consistent across platforms, then adapted the surrounding navigation and viewport. The reliable part was the calendar model; the shell was what changed with available space.

That split is the whole lesson from the BookBed property management build: some geometry is load-bearing and must not scale, and everything around it is negotiable. Deciding which is which is the design work. The breakpoints are just where you write the decision down.

Write the breakpoints down once

Material's own threshold for the navigation switch is the same 600: a bottom bar below it, a rail at or above (Flutter adaptive design, general approach). Pick the bands once, put them in a single file, and never write a raw number in a widget again. Actually, never is too strong. A widget that owns its own geometry can hold its own numbers; what must not be scattered across forty files is the breakpoint set.

Give every image an aspect ratio you chose

An image with no intended shape will take the shape of its container, and at tablet width that container is a letterbox. Community UI App's event photo is the proof. The fix is two lines of intent: wrap the image in an AspectRatio so the slot keeps a shape you picked, and set BoxFit so you know which dimension gets sacrificed when the frame and the source disagree.

The alert banner needs a different fix, and it is not a Flutter fix at all. Text baked into a bitmap cannot reflow, cannot scale with the user's text size, cannot be read by a screen reader, and gets destroyed by exactly the crop that happened here. Lift the sentence out of the artwork and render it as a Text widget over a background image or a solid surface. Then the banner can grow wide without eating its own message, and the copy survives at 200% text scale.

You have an asset like this somewhere. A promo card or an onboarding slide, with the words living inside a PNG because that was faster at the time. It works until the first wide window.

For the grid underneath, Flutter's large-screen guidance is specific: use SliverGridDelegateWithMaxCrossAxisExtent to define a max item width rather than hardcoding a column count, because "the number of columns should be based on the size of the window and not the size of the physical device," and wrap the whole thing in a ConstrainedBox with a maximum width so a reading column stays readable (Flutter on large screens and foldables). Skillz's full-width search field is one ConstrainedBox away from being fine. The same constraint discipline shows up in Flutter web SaaS dashboards, where the browser hands you an arbitrary width and nothing stops you from using all of it.

How should a layout handle a foldable hinge?

Split by width first, then let the platform move the seam: a foldable's inner display is one window with a physical obstruction running through it, and width alone cannot tell you where that obstruction sits. MediaQueryData.displayFeatures exposes "areas of the display that are obstructed by hardware features," where a hinge and a cutout obstruct the display and a fold is a crease rather than an obstruction (MediaQueryData.displayFeatures).

Two consequences follow, and the second one catches people. First, a 50/50 split computed from width can place a column directly under a hinge. Second, that property is populated only on Android and is empty everywhere else, so code that assumes a feature list will be there quietly degrades to a plain width split on iPadOS, desktop, and web. Design the width split to be correct on its own, then let display features move the seam when they exist.

State has to survive the resize

Treat every resize, rotation, and unfold as a configuration change your state must outlive. Flutter's best practices call for retaining and restoring state across device rotation, window size changes, and folding or unfolding, and recommend a PageStorageKey on scroll views so a list returns to its position rather than snapping to the top (Flutter adaptive design best practices).

This stopped being optional on Android. For apps targeting Android 16, API level 36, orientation, aspect ratio, and resizability restrictions are ignored on displays with smallest width at or above 600dp, which covers tablets, inner displays of large foldables, and desktop windowing. screenOrientation, resizeableActivity, minAspectRatio, maxAspectRatio, and setRequestedOrientation() are all ignored there. A manifest property named PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY buys you time, and the framework removes that escape hatch at API level 37 (App orientation, aspect ratio, and resizability on Android).

So the old answer, lock it to portrait and move on, now has an expiry date printed on it. Your app will be rotated and resized whether you wrote for it or not.

A test matrix you can actually run

Google's adaptive app quality guidelines, which replaced the older large screen app quality guidelines, name four configurations to test against. Run them in this order:

  1. Foldable, 841x701 dp. The awkward one. Nearly square, so width-only breakpoints land in strange places and any assumption about portrait or landscape dies here.
  2. 8-inch tablet, 1024x640 dp. This is the width where the two defects in this post appeared.
  3. 10.5-inch tablet, 1280x800 dp. Crosses into the large class, so a layout tuned for expanded gets a second look.
  4. 13-inch Chromebook, 1600x900 dp. Extra-large, resizable by the user at any moment, and the configuration that punishes an unconstrained reading column hardest.

Those dp figures come straight from the guidelines (Adaptive app quality guidelines), which sort apps into three tiers, from adaptive-ready at the bottom to adaptive-differentiated at the top.

Add a fifth pass of your own: drag a desktop window slowly from 500 px to 1600 px and watch the transitions rather than the endpoints. Most breakpoint bugs live between the sizes you tested, not at them. Checking that scroll positions and selections survive the drag costs nothing and catches the state bugs the matrix will not.

Heavier screens deserve a profiling pass too, since a two-pane layout renders both panes; Flutter list and image performance covers what that costs. And if you are still choosing a framework rather than adapting one, the React Native versus Flutter comparison is the better starting page.

Start with the one screen you already doubt

Open your app at 1024 px wide today, before any refactor, and screenshot the first viewport of your busiest screen. Then ask the only question that matters: can someone read what this screen is for without scrolling? Community UI App answered no because a crop ate the sentence. Skillz answered no because the sentence was below the fold. Both passed every build.

Which screen in your app would fail that screenshot right now?

Free resource

Free SaaS MVP Scope Template

A Notion document with the full feature checklist, MVP vs. nice-to-have table, pre-build questions, and cost signals — so you walk into any developer call knowing exactly what to ask for.

Get the template →
DL

Dusko Licanin

Full-Stack Developer · Banja Luka, Bosnia

Full-stack developer shipping SaaS MVPs, web apps, and mobile apps using AI-augmented workflows — without agency coordination overhead. Live portfolio: BookBed, Callidus, Pizzeria Bestek.

Frequently Asked Questions

What is the difference between responsive and adaptive design in Flutter?

Responsive design makes the content fit the available space, while adaptive design makes the interface usable in that space. Flutter's own documentation puts it as fitting the UI into the space versus the UI being usable in the space, and offers the clarifying question of whether a tablet should use bottom navigation or a side panel (Adaptive and responsive design in Flutter). The practical difference is how each one fails. A responsive failure throws a visible overflow error during development. An adaptive failure renders perfectly, reports nothing, and still leaves a tablet user staring at a half-empty screen or a cropped message.

At what width should Flutter switch from a bottom navigation bar to a NavigationRail?

At 600 logical pixels of window width. Flutter's guidance points at the Material layout guidelines, which suggest a bottom navigation bar for windows under 600 logical pixels wide and a navigation rail at 600 or above (Flutter adaptive design, general approach). The number that matters is the width of the window your app actually occupies, not the size of the physical screen. A phone-sized app in a resizable desktop window, a multi-window pane on a tablet, or a picture-in-picture view all sit below 600 while running on hardware you would never call a phone.

How do you handle foldable layouts in Flutter?

Split the layout by available width first, then use MediaQueryData.displayFeatures to move the seam away from the hardware. That property exposes areas of the display obstructed by hardware features, where a hinge and a cutout obstruct the screen while a fold is a crease rather than an obstruction (MediaQueryData.displayFeatures). Two details catch people. A 50/50 split computed from width alone can place a column directly under a hinge. And the list is populated only on Android, so the same code degrades to a plain width split on iPadOS, desktop and web, which means the width split has to be correct on its own.

Should you lock a Flutter app to portrait on tablets?

No, and on Android you no longer get to. For apps targeting Android 16, API level 36, orientation, aspect ratio and resizability restrictions are ignored on displays whose smallest width is 600dp or more, covering tablets, inner displays of large foldables and desktop windowing. screenOrientation, resizeableActivity, minAspectRatio, maxAspectRatio and setRequestedOrientation() are all ignored there, and the PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY opt-out disappears at API level 37 (App orientation, aspect ratio and resizability). Flutter's own best practices already listed orientation locking as something to avoid, partly because it is an accessibility problem.

What are the Flutter best practices for large screens?

Branch on window size rather than device type, constrain how wide your content is allowed to grow, and make state survive a resize. Flutter's best-practices page is explicit that your choice should not depend on the type of device but on the available window size, warns against writing if (isTablet) checks, and has a section titled around not using all the horizontal space (Flutter adaptive design best practices). For lists, swap ListView for a GridView driven by SliverGridDelegateWithMaxCrossAxisExtent so the column count follows the window, and add a PageStorageKey so scroll position survives the configuration change.