In a .net maui 10 shell app on ios 26 why does the hamburger icon background color change when navigating to a modal page

Kurt K 236 Reputation points
2026-09-25T00:59:32.3166667+00:00

The hamburger icon background color on the home page of my app changes to a darker color when I use the menu and choose the "Modal Page" menu item. Why does that happen?

Initially, here is what the home page looks like at startup:

Startup

You can see that the hamburger icon's background color is a light purple.

When I use the menu to choose the "Modal Page" menu item, the following page appears:

ModalPage

If you drag down the top of the modal a little to expose more of the home page, you will see that the icon background color is a lot darker.

Continue to drag the top of the modal all the way down to dismiss it.

The home page now looks as follows:

AfterModal

Notice how much darker the background color of the icon is.

Now on the home page tap "Show Sub Page". The following page appears:

SubPage

Notice that the background of the "back" arrow icon is the normal light purple color.

Finally, tap the back arrow to return to the home page.

You will see that the hamburger icon background is again the normal light purple color.

Another thing that I have noticed is that if Shell.BackgroundColor is set to another color (maybe lighter), for example yellow, the change in the icon background is less noticeable after showing a modal page. (When changing Shell.BackgroundColor to yellow, the Shell.ForegroundColor should be changed to black to get the icon to display.)

Do you have any thoughts on what causes the icon background color to become darker after visiting a modal page? Is there a workaround? Strange also that the behavior seems to be sensitive to the background color chosen.

Thank you.

Developer technologies | .NET | .NET Multi-platform App UI
0 comments No comments

Answer accepted by question author
Harry Nguyen (WICLOUD CORPORATION) 95 Reputation points Microsoft External Staff Moderator
2026-09-30T11:49:45.7+00:00

Hi @Kurt K , 

Thanks for putting the repo together and testing on a real device. 

First, a quick correction on my earlier wording. By "light purple" I meant the color you see around the hamburger icon, not the Shell.BackgroundColor value. #521B3B was the right value, so your test was set up correctly. On iOS 26 the bar button sits on a Liquid Glass background, which is likely why the area around the icon looks lighter than #521B3B itself. 

The FormSheet vs FullScreen comparison is the key finding. When a sheet is presented, UIKit dims the views underneath it and is expected to restore them once the sheet is gone. After a FormSheet is dismissed, the hamburger button in the Shell navigation bar appears to stay in that dimmed state. Going to the Sub Page and back fixes it because the navigation bar button gets recreated. FullScreen is handled differently by UIKit, which would explain why it doesn't show the issue. 

I haven't confirmed this in the MAUI source, but it matches everything you've observed. It also means the issue sits in the interaction between UIKit's sheet presentation and how Shell manages its navigation bar, so there's limited room to fix it cleanly from app code. 

Using your sample, I built an iOS-only workaround that keeps FormSheet and swipe-to-dismiss. The idea is to reproduce what the Sub Page round trip does, but automatically when the modal closes: 

Hook into the native presentation controller so the swipe-to-dismiss gesture is also detected, not just button dismissal 

When the modal starts dismissing, force the tint mode back to Normal 

Once it's dismissed, push and immediately pop an empty view controller on the root navigation controller so the hamburger button is rebuilt 

Platforms/iOS/NavBarFix.cs 

#if IOS 
using UIKit; 
namespace MauiShellTitleView2; 
/// <summary> 
/// Mimics what happens when navigating to SubPage and back: 
/// push an empty VC -> the hamburger button is removed -> pop immediately -> 
/// MAUI recreates the hamburger button -> icon returns to its normal state. 
/// </summary> 
public static class NavBarFix 
{ 
    private static bool _isRefreshing; 
    public static void ForceNavigationRefresh(bool force = false) 
    { 
        if (_isRefreshing) return; 
        _isRefreshing = true; 
        try 
        {
            foreach (var scene in UIApplication.SharedApplication.ConnectedScenes.OfType<UIWindowScene>()) 
            { 
                foreach (var window in scene.Windows) 
                { 
                    ForceNormalTintMode(window); 
                    if (window.RootViewController != null) 
                        TryRefreshOnController(window.RootViewController, force); 
                } 
            } 
        } 
        catch (Exception ex) 
        { 
            Console.WriteLine($"[NavBarFix] Error: {ex.Message}"); 
        } 
        finally 
        { 
            _isRefreshing = false; 
        } 
    } 
    public static void ForceNormalOnAllViews() 
    { 
        try 
        { 
            foreach (var scene in UIApplication.SharedApplication.ConnectedScenes.OfType<UIWindowScene>()) 
                foreach (var window in scene.Windows) 
                    ForceNormalTintMode(window); 
        } 
        catch { } 
    } 
    private static void TryRefreshOnController(UIViewController vc, bool force) 
    { 
        // Only run on the root page (where the hamburger button lives). 
        // Never run when the stack has more than one controller, 
        // otherwise the Back button on pages like SubPage would be destroyed. 
        if (vc is UINavigationController nav && nav.ViewControllers?.Length == 1) 
        { 
            if (!force && nav.PresentedViewController != null) 
                return; 
            if (nav.View != null) 
                ForceNormalTintMode(nav.View); 
            // 1. Reassign the left bar button item 
            var item = nav.TopViewController?.NavigationItem; 
            if (item?.LeftBarButtonItem != null) 
            { 
                var btn = item.LeftBarButtonItem; 
                item.LeftBarButtonItem = null; 
                item.LeftBarButtonItem = btn; 
            } 
            // 2. Push an empty VC without animation, then pop it immediately 
            nav.PushViewController(new UIViewController(), false); 
            nav.PopViewController(false); 
            // 3. Reapply Normal tint mode on the rebuilt navigation bar 
            ForceNormalTintMode(nav.NavigationBar); 
            nav.NavigationBar.SetNeedsLayout(); 
            nav.NavigationBar.LayoutIfNeeded(); 
            return; 
        } 
        foreach (var child in vc.ChildViewControllers) 
            TryRefreshOnController(child, force); 
    }
    public static void ForceNormalTintMode(UIView view) 
    { 
        try 
        { 
            view.TintAdjustmentMode = UIViewTintAdjustmentMode.Normal; 
            foreach (var sub in view.Subviews) 
                ForceNormalTintMode(sub); 
        } 
        catch { } 
    } 
} 
#endif 

Platforms/iOS/GlobalModalWatcher.cs

#if IOS 
using UIKit; 
using Microsoft.Maui.Controls; 
namespace MauiShellTitleView2; 
public static class GlobalModalWatcher 
{ 
    private static GlobalAdaptiveDelegate? _strongDelegate; 
    private static bool _isInitialized; 
    public static void Initialize() 
    { 
        if (_isInitialized) return; 
        _isInitialized = true; 
        if (Application.Current != null) 
        { 
            Application.Current.PageAppearing += OnPageAppearing; 
            Application.Current.PageDisappearing += OnPageDisappearing; 
            Application.Current.ModalPopped += OnModalPopped; 
        } 
        // Covers modals closed with a button or GoToAsync 
        if (Shell.Current != null) 
            Shell.Current.Navigated += OnShellNavigated; 
    } 
    private static bool IsModal(Page page) => 
        page is ModalPage || 
        page is ModalPage_Full || 
        Shell.GetPresentationMode(page).HasFlag(PresentationMode.Modal); 
    private static void OnPageAppearing(object? sender, Page page) 
    { 
        if (IsModal(page) || (page.Navigation?.ModalStack.Contains(page) ?? false)) 
            TryAttachDismissDelegate(); 
    } 
    private static void OnPageDisappearing(object? sender, Page page) 
    { 
        if (IsModal(page)) 
            MainThread.BeginInvokeOnMainThread(NavBarFix.ForceNormalOnAllViews); 
    } 
    private static void OnModalPopped(object? sender, ModalPoppedEventArgs e) => 
        MainThread.BeginInvokeOnMainThread(() => NavBarFix.ForceNavigationRefresh(force: true)); 
    private static void OnShellNavigated(object? sender, ShellNavigatedEventArgs e) 
    { 
        bool wasModal = e.Previous?.Location.OriginalString 
            .Contains("modal", StringComparison.OrdinalIgnoreCase) ?? false; 
        bool isMainPage = e.Current?.Location.OriginalString.Contains("MainPage") ?? false; 
        if (wasModal || isMainPage) 
            MainThread.BeginInvokeOnMainThread(() => NavBarFix.ForceNavigationRefresh(force: true)); 
    } 
    // Attach a delegate to the presented controller so swipe-to-dismiss is detected 
    public static void TryAttachDismissDelegate() 
    { 
        MainThread.BeginInvokeOnMainThread(async () => 
        { 
            for (int i = 0; i < 15; i++) 
            { 
                var pc = GetTopPresentedViewController()?.PresentationController; 
                if (pc != null) 
                { 
                    if (pc.Delegate is not GlobalAdaptiveDelegate) 
                    { 
                        _strongDelegate = new GlobalAdaptiveDelegate(pc.Delegate); 
                        pc.Delegate = _strongDelegate; 
                    } 
                    return; 
                } 
                await Task.Delay(50); 
            } 
        }); 
    } 
    private static UIViewController? GetTopPresentedViewController() 
    { 
        foreach (var scene in UIApplication.SharedApplication.ConnectedScenes.OfType<UIWindowScene>()) 
        { 
            foreach (var window in scene.Windows) 
            { 
                var root = window.RootViewController; 
                if (root == null) continue; 
                var top = root; 
                while (top.PresentedViewController != null) 
                    top = top.PresentedViewController; 
                if (top != root) return top; 
            } 
        } 
        return null; 
    } 
    // Wraps the original delegate and forwards calls to it, 
    // so MAUI's own dismissal handling still runs 
    private class GlobalAdaptiveDelegate : UIAdaptivePresentationControllerDelegate 
    {
        private readonly IUIAdaptivePresentationControllerDelegate? _original; 
        public GlobalAdaptiveDelegate(IUIAdaptivePresentationControllerDelegate? original) => 
            _original = original; 
        public override void WillDismiss(UIPresentationController presentationController) 
        { 
            try { _original?.WillDismiss(presentationController); } catch { } 
            NavBarFix.ForceNormalOnAllViews(); 
        } 
        public override void DidDismiss(UIPresentationController presentationController) 
        { 
            try { _original?.DidDismiss(presentationController); } catch { } 
            NavBarFix.ForceNavigationRefresh(force: true); 
        } 
    } 
} 
#endif 

Platforms/iOS/AppDelegate.cs

using Foundation; 
namespace MauiShellTitleView2 
{ 
    [Register("AppDelegate")] 
    public class AppDelegate : MauiUIApplicationDelegate 
    { 
        protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
        public override bool FinishedLaunching(UIKit.UIApplication application, NSDictionary launchOptions)
        { 
            var result = base.FinishedLaunching(application, launchOptions); 
            GlobalModalWatcher.Initialize(); 
            return result; 
        } 
    } 
} 

No changes are needed in MainPage, ModalPage or SubPage. The modal stays on FormSheet, so swipe-to-dismiss is kept.

Please note that this doesn't resolve the behavior consistently in every case yet. It also relies on replacing the presentation controller's delegate and briefly modifying the UIKit navigation stack underneath Shell. Both can interact with how MAUI tracks modal and navigation state, so I'd treat it as something to evaluate rather than a production-ready fix. If you try it, please also check that the Sub Page's Back button still works as expected.

If you found the analysis and workaround helpful, I would greatly appreciate it if you could follow this guide for your confirmation.

Was this answer helpful?

1 person found this answer helpful.

2 additional answers

Sort by: Newest
  1. Harry Nguyen (WICLOUD CORPORATION) 95 Reputation points Microsoft External Staff Moderator
    2026-09-25T02:41:01.02+00:00

    Hi @Kurt K ,

    Thank you for the detailed write-up and screenshots — the step-by-step comparison between the modal flow and the Sub Page flow is very helpful for narrowing this down.

    Before running the broader style tests, there are two quick things worth trying, since they can isolate the behavior immediately.

    1. Set an explicit iOS modal presentation style on the Modal Page

    By default the presentation style is Automatic, which lets iOS pick a sheet-style presentation — and the interactive drag-to-dismiss gesture is exactly the flow where you're seeing the darkening.

    In XAML:

    <ContentPage ...
                 xmlns:ios="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls"
                 ios:Page.ModalPresentationStyle="FullScreen">
    </ContentPage>
    

    Or in code-behind:

    using Microsoft.Maui.Controls.PlatformConfiguration;
    using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
    
    On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.FullScreen);
    

    FullScreen removes the drag gesture. If the hamburger background stays correct afterwards, the behavior is tied to the sheet presentation rather than to Shell itself.

    2. Let the page provide the visible background instead of the Shell

    Set Shell.BackgroundColor="Transparent" and move the light purple to ContentPage.BackgroundColor. If the darkening disappears, it points to the Shell chrome being composited by iOS rather than the color value actually being changed.

    Please note these are diagnostic steps to isolate the behavior, not a confirmed fix. I haven't reproduced this on my side yet, so I'd still like to verify the root cause before recommending anything permanent.

    If the darkening is still present after those two tests, simplifying the Shell appearance configuration will help confirm whether Shell styling is contributing. Temporarily use fixed colors at the Shell level:

    <Shell
    ...
    Shell.BackgroundColor="#E8D7FF"
    Shell.ForegroundColor="Black"
    Shell.TitleColor="Black">
    

    Then run the same modal navigation flow and check the following:

    • Try a few different Shell.BackgroundColor values and see whether the amount of darkening changes.
    • Check whether Shell.BackgroundColor or Shell.ForegroundColor is being overridden by styles on the Home Page or Modal Page.
    • If you're using DynamicResource or AppThemeBinding, temporarily replace them with fixed color values.
    • Test the same fixed colors in both Light Mode and Dark Mode.
    • After dismissing the Modal Page, check the hamburger background before navigating to the Sub Page.

    Since you've already observed that changing Shell.BackgroundColor affects how noticeable the darkening is, these tests should help confirm whether the Shell/theme configuration is involved. The fact that normal navigation to the Sub Page and back restores the expected background also suggests that modal and regular navigation refresh the Shell appearance differently — though I wouldn't call that the root cause until it's reproduced.

    To help narrow this down, could you share a minimal code sample showing:

    1. The relevant Shell configuration and styles.
    2. The code used to open and dismiss the Modal Page.
    3. The code used to navigate to and back from the Sub Page.

    It would also be helpful to know your .NET MAUI version, Xcode version, exact iOS 26.x version, and whether you're testing on a physical device or simulator.

    If the project is based on a Microsoft Learn sample, please also share the sample link and any changes you've made to the Shell or navigation code. That would make it easier to reproduce the same navigation flow and isolate the behavior.

    If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation.

    Was this answer helpful?

    1 person found this answer helpful.

  2. AI answer

    2026-09-25T01:00:05.6666667+00:00

    This is caused by iOS modal presentation behavior, not by Shell navigation itself.

    In .NET MAUI on iOS, modal pages use an iOS-specific presentation style. The documentation states that modal pages can be shown with styles such as FullScreen, PageSheet, FormSheet, Popover, and OverFullScreen, and that the system chooses the default style when Automatic is used. When a modal is presented and then interactively dismissed, iOS can visually composite the underlying page differently while the modal is active or transitioning. That matches the behavior shown here: the Shell chrome behind the modal appears darker, while normal push navigation to a subpage restores the usual appearance.

    The color sensitivity also fits the platform rendering behavior. Shell page colors affect the Shell chrome, and iOS can blend that chrome differently depending on the chosen color, which makes the darkening more or less noticeable.

    A practical workaround is to explicitly set the modal presentation style on the modal page and test which style gives the most consistent result on iOS. In XAML:

    <ContentPage ...
                 xmlns:ios="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls"
                 ios:Page.ModalPresentationStyle="FullScreen">
    </ContentPage>
    

    Or in C#:

    using Microsoft.Maui.Controls.PlatformConfiguration;
    using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
    
    On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.FullScreen);
    

    If the goal is to minimize visible Shell chrome artifacts on iOS, another useful approach is to avoid painting the Shell background and let the page provide the visible background instead. A Microsoft Q&A answer for a similar Shell color artifact on iOS explains that setting the Shell background to Transparent lets the ContentPage.BackgroundColor be the visible background and avoids the Shell container separator effect.

    So the most relevant options are:

    1. Set an explicit iOS modal presentation style on the modal page, especially FullScreen.
    2. If the Shell chrome itself is the part showing the artifact, try Shell.BackgroundColor="Transparent" and move the visible background color to the page.
    3. Expect the effect to vary by color because iOS blending makes darker shades more noticeable.

    There is no context here that identifies this as a documented Shell bug with a dedicated fix. The supported explanation is that it is tied to iOS rendering of modal presentation and Shell chrome appearance.


    AI-generated content may be incorrect. Read our transparency notes for more information.

    Was this answer helpful?

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.