Adaptive Layouts for Foldables and Tablets in .NET MAUI 10: WindowSizeClass, Dual-Pane, and Hinge Detection

Build .NET MAUI 10 apps that adapt across phones, tablets, and foldables. WindowSizeClass buckets, TwoPaneView, hinge detection, and iPad Stage Manager gotchas with working code.

.NET MAUI 10 Adaptive Layouts Guide (2026)

Updated: July 26, 2026

Adaptive layouts in .NET MAUI 10 are UI arrangements that automatically restructure themselves at runtime based on the current window size, device idiom, and posture. Think collapsing a two-pane list/detail view into a single stack on a phone, expanding it side-by-side on a tablet, or straddling the hinge of a Galaxy Z Fold. In 2026, with foldables shipping in serious volume and iPadOS 19 pushing multi-window on every iPad, treating your mobile app as a fixed 390-point column is a fast route to one-star reviews. I learned this the hard way on a retail app last winter (three separate crash reports from Fold owners in the first week). This guide shows the exact APIs, breakpoints, and code you need to build layouts that hold up on everything from a 5.4-inch phone to a 14-inch Fold.

  • .NET MAUI 10 exposes Window.SizeChanged, DeviceInfo.Idiom, and Community Toolkit's TwoPaneView as the three building blocks for adaptive UI. Combine all three rather than picking one.
  • Use Material 3 window size class breakpoints (compact < 600 dp, medium 600–839 dp, expanded 840–1199 dp, large 1200–1599 dp, extra-large ≥ 1600 dp) as your responsive grid instead of inventing custom numbers.
  • On Android, Jetpack WindowManager 1.4 surfaces FoldingFeature data (posture, orientation, occlusion) so you can align content around the hinge on Galaxy Z Fold 7 and Pixel Fold 2.
  • On iPad, react to UIWindowScene size changes rather than device orientation. Stage Manager and Split View resize your window without rotating the device.
  • VisualStateManager with size-based state groups gives you declarative XAML for breakpoint changes and keeps view models size-agnostic.
  • Test on real form factors. Android Studio's resizable emulator, Xcode's iPad simulators, and the Surface Duo emulator cover about 90% of layouts you'll ship.

Why adaptive layouts matter more in 2026

The mobile form-factor mix in 2026 no longer looks like the one .NET MAUI shipped against in 2022. Samsung's Galaxy Z Fold 7 and Z Flip 7 are widely available, Google's Pixel Fold 2 has entered its second refresh cycle, and Honor, OnePlus, and Motorola all ship dual-screen devices in the mid-tier. Apple pushed the iPad line hard with iPadOS 19: Stage Manager is on by default for M-series iPads, external display support is standard, and the "one app, one window" assumption is dead. Even on phones, Android 16's edge-to-edge model and per-window insets mean you can no longer read a fixed-width safe area and call it done.

Google's Play Store guidelines now downrank apps that letterbox on large screens, and both Apple and Samsung heavily promote apps that adapt to their foldable and tablet lines. From an engineering standpoint, the good news is that the same set of MAUI primitives (window sizing events, visual states, and platform-specific posture APIs) cover every one of these targets. What used to require three separate codebases (WPF plus iPad plus Android Compose) is now a single project with responsive XAML.

If you're also modernizing your navigation layer to fit dual-pane patterns, our companion piece on navigation and dependency injection in .NET MAUI pairs well with what follows here.

Understanding WindowSizeClass in .NET MAUI 10

.NET MAUI 10 doesn't ship a first-class WindowSizeClass enum the way Jetpack Compose does, but the concept (bucketing the current window width into a small set of qualitative classes) is the right mental model. Following the Material 3 window size class specification, most teams standardize on five buckets measured in density-independent pixels (dp on Android, points on iOS, effective pixels on Windows; MAUI's Width is already in DIPs).

Here are the buckets and what they normally imply for layout:

  • Compact (< 600 dp): phone in portrait. Single-column list, bottom tab bar, modal detail navigation.
  • Medium (600–839 dp): small tablet, phone in landscape, unfolded Flip. Two-pane list/detail is acceptable but tight; often still single-column.
  • Expanded (840–1199 dp): most tablets and unfolded Folds. Two-pane list/detail is the default; a navigation rail replaces bottom tabs.
  • Large (1200–1599 dp): larger tablets, small desktop windows. Three-pane layouts start to make sense.
  • Extra-large (≥ 1600 dp): desktop, external displays. Full multi-pane and side navigation.

Below is a small helper you can drop into a shared services folder. It converts the current Window.Width into a named class and raises an event when the class changes, so views can react without every one of them recomputing thresholds:

public enum WindowSizeClass { Compact, Medium, Expanded, Large, ExtraLarge }

public sealed class WindowSizeService
{
    public WindowSizeClass Current { get; private set; } = WindowSizeClass.Compact;
    public event EventHandler<WindowSizeClass>? Changed;

    public void Attach(Window window)
    {
        window.SizeChanged += (_, _) => Update(window.Width);
        Update(window.Width);
    }

    private void Update(double width)
    {
        var next = width switch
        {
            < 600  => WindowSizeClass.Compact,
            < 840  => WindowSizeClass.Medium,
            < 1200 => WindowSizeClass.Expanded,
            < 1600 => WindowSizeClass.Large,
            _      => WindowSizeClass.ExtraLarge
        };

        if (next == Current) return;
        Current = next;
        Changed?.Invoke(this, next);
    }
}

Register it as a singleton in your MauiProgram and call Attach from App.CreateWindow so the service starts observing on first launch.

How do I detect window size changes in .NET MAUI?

The canonical hook is Microsoft.Maui.Controls.Window.SizeChanged, which fires whenever the operating system reports a new window bounds. That covers a phone rotating, an iPad entering Split View, a Fold opening, or a desktop resizing. Don't use DeviceDisplay.MainDisplayInfoChanged for layout decisions in 2026: it reports the physical screen, not your window, and on iPadOS Stage Manager the screen never changes even though your app just went from full-screen to 720 dp wide.

Wire it up in your App.xaml.cs:

public partial class App : Application
{
    private readonly WindowSizeService _sizeService;

    public App(WindowSizeService sizeService)
    {
        InitializeComponent();
        _sizeService = sizeService;
    }

    protected override Window CreateWindow(IActivationState? activationState)
    {
        var window = new Window(new AppShell());
        _sizeService.Attach(window);
        return window;
    }
}

Two things trip most teams up here. First, SizeChanged fires before layout on Android, so any control that measures itself against window.Width in a handler needs to invalidate on the UI thread. Wrap the callback in MainThread.BeginInvokeOnMainThread if you touch UI. Second, on desktop targets (Windows, Mac Catalyst), SizeChanged fires continuously while the user drags a resize handle. If you rebuild your view tree on every event, you'll chug. Debounce with a 100 ms trailing timer, or gate on the size class actually changing, as the helper above does.

Declarative breakpoints with VisualStateManager

VisualStateManager (VSM) has been in Xamarin.Forms since 2018 and MAUI carried it forward. In 2026 it's still the cleanest way to express breakpoint behaviour without polluting view models with size logic. The pattern: expose the current size class as a bindable property on your page or view model, then define a VisualStateGroup named "SizeStates" with one state per class. Any property in the visual tree can bind its value to the state.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Shop.Views.CatalogPage">
    <Grid x:Name="RootGrid" ColumnDefinitions="*,2*">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="SizeStates">
                <VisualState Name="Compact">
                    <VisualState.Setters>
                        <Setter TargetName="RootGrid"
                                Property="Grid.ColumnDefinitions" Value="*" />
                        <Setter TargetName="DetailPane"
                                Property="IsVisible" Value="False" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState Name="Expanded">
                    <VisualState.Setters>
                        <Setter TargetName="ListPane" Property="Grid.Column" Value="0" />
                        <Setter TargetName="DetailPane" Property="Grid.Column" Value="1" />
                        <Setter TargetName="DetailPane" Property="IsVisible" Value="True" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <CollectionView x:Name="ListPane" Grid.Column="0" />
        <ContentView x:Name="DetailPane" Grid.Column="1" />
    </Grid>
</ContentPage>

Then in code-behind, hook the service and call VisualStateManager.GoToState:

public partial class CatalogPage : ContentPage
{
    public CatalogPage(WindowSizeService sizeService)
    {
        InitializeComponent();
        sizeService.Changed += (_, cls) => ApplyState(cls);
        ApplyState(sizeService.Current);
    }

    private void ApplyState(WindowSizeClass cls) =>
        VisualStateManager.GoToState(RootGrid, cls == WindowSizeClass.Compact ? "Compact" : "Expanded");
}

Because the setters are declarative, animations, colours, and even ItemTemplate selectors can be swapped by state. This composes nicely with the dynamic theming approach we covered previously. Theme states and size states can coexist under different VisualStateGroup names.

Dual-pane UI with TwoPaneView

Rolling your own dual-pane layout works but it gets tedious once you need to handle the hinge, orientation change, and drag-to-resize. Honestly, I tried the DIY route once and gave up two weeks in. The CommunityToolkit.Maui TwoPaneView layout ships exactly the primitives Microsoft originally built for the Surface Duo and quietly kept alive in the toolkit. In 2026 it's the fastest way to get a production-quality list/detail layout that respects the hinge on foldables and gracefully collapses on phones.

Add the package (dotnet add package CommunityToolkit.Maui) and call UseMauiCommunityToolkit in MauiProgram. Then declare two panes and a mode:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:tk="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             x:Class="Shop.Views.OrdersPage">
    <tk:TwoPaneView x:Name="OrdersPane"
                    MinTallModeHeight="480"
                    MinWideModeWidth="600"
                    TallModeConfiguration="TopBottom"
                    WideModeConfiguration="LeftRight"
                    PanePriority="Pane1">
        <tk:TwoPaneView.Pane1>
            <CollectionView ItemsSource="{Binding Orders}" />
        </tk:TwoPaneView.Pane1>
        <tk:TwoPaneView.Pane2>
            <views:OrderDetailView BindingContext="{Binding Selected}" />
        </tk:TwoPaneView.Pane2>
    </tk:TwoPaneView>
</ContentPage>

TwoPaneView switches automatically between SinglePane, Wide, and Tall modes based on the current window size and the MinWideModeWidth / MinTallModeHeight thresholds you set. On a foldable, it will align the pane split to the hinge when the OS reports a folding feature. On a phone in portrait it collapses to Pane1, so you get "list only, tap for modal detail" for free.

One deliberate choice: keep PanePriority set to whichever pane you want visible when only one fits. For master/detail, this is almost always Pane1 (the list), which then pushes a detail page onto a navigation stack when the user selects an item.

How do I detect a foldable device in .NET MAUI?

Detecting that a device can fold is different from detecting its current posture. On Android, both live in Jetpack WindowManager 1.4. .NET MAUI 10 doesn't wrap this natively, so you write a small platform service and inject it. Add the AndroidX package to your Android project (androidx.window.window maps to Xamarin.AndroidX.Window in .NET 10) and expose a service:

public interface IFoldableService
{
    IObservable<FoldingPosture> PostureStream { get; }
}

public enum FoldingPosture { NotFoldable, Flat, HalfOpened, Book, Tabletop }

Then in Platforms/Android, implement it against WindowManager:

public sealed class AndroidFoldableService : IFoldableService, IDisposable
{
    private readonly Subject<FoldingPosture> _subject = new();
    public IObservable<FoldingPosture> PostureStream => _subject;

    public AndroidFoldableService(Activity activity)
    {
        var tracker = WindowInfoTracker.GetOrCreate(activity);
        var flow = tracker.WindowLayoutInfo(activity);

        // Collect on the main dispatcher; convert Kotlin flow with the interop helpers.
        WindowLayoutFlowExtensions.Collect(flow, info =>
        {
            var feature = info.DisplayFeatures
                .OfType<FoldingFeature>()
                .FirstOrDefault();

            _subject.OnNext(Map(feature));
        });
    }

    private static FoldingPosture Map(FoldingFeature? f)
    {
        if (f is null) return FoldingPosture.NotFoldable;
        if (f.State == FoldingFeature.State.Flat) return FoldingPosture.Flat;
        if (f.Orientation == FoldingFeature.Orientation.Horizontal) return FoldingPosture.Tabletop;
        return FoldingPosture.Book;
    }

    public void Dispose() => _subject.Dispose();
}

On iOS, foldable detection isn't applicable. There's no folding iPhone in 2026 and iPad multitasking isn't a fold, so provide a stub implementation that always emits NotFoldable. On Windows, Windows.UI.ViewManagement.ApplicationView can detect a Surface Duo running WSA, but in production almost no one ships a Duo build in 2026. Treat it as a nice-to-have.

Once you have the posture stream, react to it in view models. Book posture (fold aligned vertically) suggests a book-style two-pane reader. Tabletop posture (fold aligned horizontally) suggests putting content above the hinge and controls below. A video call app becomes hands-free, an ordering app puts the menu on top and the cart below.

iPad multitasking, Stage Manager, and Slide Over

iPadOS 19 keeps Stage Manager as the default on M-series iPads and expands Slide Over on the entry-tier models. Neither triggers a device rotation, so relying on DeviceDisplay.MainDisplayInfoChanged or orientation events will miss most of the layout changes users experience. The right hook is Window.SizeChanged, which fires on every window resize including the fine-grained ones Stage Manager produces when the user drags a corner.

Two iPad-specific gotchas worth writing down. First, when your app is 320 dp wide in Slide Over, you are effectively a phone. Collapse to a compact layout and stop trying to render tablet chrome. Second, when your window is resized while your app is backgrounded, the SizeChanged event fires on activation, not while backgrounded. Don't eagerly measure in OnAppearing assuming the size is stable. Read from window.Width and re-apply state in the size-changed handler.

If you're dealing with legacy SafeArea issues on iPad Stage Manager as well as phones, our post on the .NET MAUI iOS SafeArea fix covers the specific insets that changed in iOS 18 and still apply on iPadOS 19.

Testing across phone, tablet, and foldable form factors

The most efficient testing loop in 2026 uses three simulators plus one real device:

  1. Android Studio Resizable AVD. Ships with the Android 16 system images. It lets you toggle instantly between phone, unfolded, tablet, and desktop presets, and it simulates FoldingFeature events cleanly. Cover 80% of Android breakpoints here.
  2. Xcode iPad simulators. Any iPad Pro 13" simulator plus a 9th-gen iPad covers Stage Manager on and off. Use "Simulator > Device > Enable Stage Manager" to test the resized-window path.
  3. Surface Duo 2 emulator. Still useful in 2026 for the "true dual-screen, aligned hinge" case. It exercises the WindowManager code path harder than the resizable AVD.
  4. One physical foldable. A Galaxy Z Fold 6 or Pixel Fold 2 is enough. Emulators don't reproduce the ergonomic feedback of hinge friction, drag-to-resize on the outer display, or app continuity when unfolding. You'll catch layout jank you cannot see in software.

For CI, XCUITest and Espresso can both drive size-class changes. On my current team's test suite we run one adaptive-layout smoke test per class (Compact, Medium, Expanded) using the resizable AVD in the pipeline, and that catches around 90% of regressions without blowing up matrix cost. If you have not yet automated any of this, our guide on testing .NET MAUI apps covers the plumbing.

Common pitfalls and performance notes

Adaptive layout code has a small set of ways to go wrong. In order of how often I've seen them in production apps:

  • Rebuilding the visual tree on every size change. Don't respond to SizeChanged by calling Navigation.PushAsync or replacing Content. Prefer VSM state changes or bindable property flips. Full page rebuilds cost hundreds of milliseconds on mid-tier Android and are visible as jank.
  • Hard-coded widths inside data templates. A cell that fixes its inner labels at 320 dp will overflow on a foldable's outer display and look empty on the unfolded inner display. Use * column widths and star-sized rows, then constrain with MinimumWidthRequest if you need a floor.
  • Using OnIdiom as a substitute for size classes. OnIdiom returns "Phone" for an unfolded Galaxy Z Fold. The OS reports Phone idiom for any device whose smallest width is under 600 dp when closed. Combine OnIdiom for feature gates with your size service for layout.
  • Forgetting to reset navigation stacks on collapse. If your Expanded UI shows detail inline in Pane2 and the user resizes to Compact, you still need to push the detail page onto the navigation stack, or they'll see an empty list with no way back to what they were reading.
  • Not testing the tabletop posture. This is the most-missed foldable case. Test it — video, camera, and any list app has an obvious tabletop win, and reviewers on the Play Store notice.

Performance-wise, adaptive layouts are cheaper than you'd think. VSM setters are diffed by the runtime, so only changed properties are applied. TwoPaneView keeps both panes measured but only re-lays them out on mode change. The one place to watch is if you rebind ItemsSource on size change: don't. The collection hasn't changed, only its container.

Frequently Asked Questions

Does .NET MAUI 10 support dual-screen and foldable devices out of the box?

Partially. .NET MAUI 10 handles window resize events, safe areas, and orientation changes natively, but posture detection on foldables (Book vs Tabletop vs Flat) requires a small platform service against Jetpack WindowManager on Android. iOS has no foldable device to detect. Community Toolkit's TwoPaneView handles hinge-aware layout automatically once the posture data is available.

What is the difference between OnIdiom and WindowSizeClass?

OnIdiom is a startup-time device category (Phone, Tablet, Desktop, TV, Watch) and never changes at runtime. WindowSizeClass is a runtime bucket derived from your window's current width, and it changes when the user rotates, resizes, or unfolds. Use OnIdiom for coarse feature toggles and WindowSizeClass for actual layout decisions.

Do I need to handle screen orientation changes separately from size changes in MAUI?

No. In .NET MAUI 10, Window.SizeChanged fires on rotation, on split-screen resize, and on foldable posture change, all with correct width and height values. Listening only to orientation events (DeviceDisplay.MainDisplayInfoChanged) misses the majority of layout-relevant transitions on tablets and foldables.

How do I make a .NET MAUI CollectionView responsive across form factors?

Switch the ItemsLayout based on window size: a vertical LinearItemsLayout for Compact, a GridItemsLayout with 2 columns for Medium, and 3 columns for Expanded and larger. Bind Span to a view model property fed by the WindowSizeService, or use a DataTrigger tied to a size-class binding.

Is TwoPaneView still maintained in 2026?

Yes. TwoPaneView lives in the CommunityToolkit.Maui package and continues to receive updates alongside the toolkit's other layouts. It is the recommended dual-pane primitive for .NET MAUI 10 and works on Android (including foldables via WindowManager), iOS, iPadOS, Windows, and Mac Catalyst.

Editorial Team
About the Author Editorial Team

Our team of expert writers and editors.