Skip to content

Migrating to Popup v2

Shaun Lawrence edited this page Jun 5, 2025 · 12 revisions

We recently shipped version 2 of the Popup in the .NET MAUI Community Toolkit.

This comes with a number of breaking changes but we promise that you are going to love the result!

Why did we break things?

We did try hard to make a seamless migration path however this was just not feasible. We experienced an overwhelming number of issues in the original popup implementation and made the hard decision to drop and replace it with something we believe will be much more stable. The sheer fact that the Pull Request introducing popup v2 closed 33 issues just goes to show the impact!

The original implementation made use of platform specific implementations to show the popup (e.g. UIPopoverPresentationController on iOS and macOS) while this worked well at the general presentation of a view it lead to many issues within our codebase aimed at dealing with layouts and sizing of the content - this is something that MAUI already does for us.

Migrating from v1 to v2

If you are in the process of migrating from v1 to v2 or are about to begin that journey the following article is aimed at helping.

The IPopupService interface has moved namespaces

We have moved IPopupService from CommunityToolkit.Maui.Core to CommunityToolkit.Maui. You will likely need to update any using statements in the files where you use IPopupService from using CommunityToolkit.Maui.Core; to using CommunityToolkit.Maui;.

Enabled support for showing the same popup instance multiple times

Yes we understand that this was a rather limited factor with the old implementation, thankfully the new approach makes it possible to show the same instance multiple times. This improvement was the influence behind the next improvement:

Introduced AddSingletonPopup and AddScopedPopup

Now that popups can be displayed multiple times we have provided support for registering popups as singleton or scoped services.

Removed the need to subclass Popup

You can now use any View as the popup content without needing to inherit from Popup. The only caveat to this rule is - if you need to return a result from the popup, you have to inherit from Popup.

Replaced properties

Popup now inherits from ContentView which provides similar properties to what had been added to the original Popup implementation. In order to provide a much more consistent approach with how developers build their user interfaces we took the decision to shift over to the using the properties that come out of the box from .NET MAUI, therefore the following has been modified:

Size is replaced with WidthRequest and HeightRequest

<toolkit:Popup 
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    x:Class="MyProject.SimplePopup"
-   Size="100,200"
+   WidthRequest="100"
+   HeightRequest="200">
    
</toolkit:Popup>

Color is replaced with BackgroundColor

<toolkit:Popup 
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    x:Class="MyProject.SimplePopup"
-   Color="Green"
+   BackgroundColor="Green">
    
</toolkit:Popup>

CanBeDismissedByTappingOutsideOfPopup has been moved

This can now be controller by the PopupOptions class.

Improved styling out of the box

Popups now have their content wrapped inside a Border control making it easy to style without having to increase the complexity of your popup view. If you want to disable this default Border rendering then read on into PopupOptions.

This can now be controller by the PopupOptions class.

PopupOptions

When displaying a popup through the IPopupService, Page, Shell or INavigation, developers can now pass in options that customize the behavior and appearance of the displayed popup.

Note that each of the following properties are BindableProperty definitions meaning you can change them dynamically and control the visuals of the Popup from bindings.

CanBeDismissedByTappingOutsideOfPopup

PopupOptions provides the CanBeDismissedByTappingOutsideOfPopup property which can control the whether the user can tap or click outside of the Popup bounds to dismiss the currently displayed Popup. This is true by default. The following example shows how to prevent a user from being able to dismiss the Popup.

await this.ShowPopupAsync(new SimplePopup(), new PopupOptions
{
    CanBeDismissedByTappingOutsideOfPopup = false
});

Note

It is possible to cache PopupOptions to reuse on future navigation actions or with other popups that share the same configuration, that would prevent unnecessary allocations.

PageOverlayColor

PopupOptions provides the PageOverlayColor property which can control the Color that overlays the current page. The following example shows how to set the PageOverlayColor to orange.

await this.ShowPopupAsync(new SimplePopup(), new PopupOptions
{
    PageOverlayColor = Colors.Orange
});

The above Popup renders with an opaque background, to control the opacity modify the alpha property of a Color. The following example shows how to

await this.ShowPopupAsync(new SimplePopup(), new PopupOptions
{
    PageOverlayColor = Colors.Orange.WithAlpha(0.5f)
});

OnTappingOutsideOfPopup

This property is a new introduction and allows developers to receive a callback when, as the name suggests, the popup is closed by tapping outside.

Shape

PopupOptions provides the Shape property which can control the appearance of the border around the content displayed in the Popup. The following example shows how to set the border to be a rounded rectangle with a corner radius of 4 and a stroke of blue.

await this.ShowPopupAsync(new SimplePopup(), new PopupOptions
{
    Shape = new RoundRectangle
    {
        CornerRadius = new CornerRadius(4),
        Stroke = Colors.Blue,
        StrokeThickness = 4
    }
});

For more details on how to customize the Shape property see .NET MAUI Shapes.

Disable the Popup Border

In order to disable the border on a Popup, simply set the Shape property to null. The following example shows how to achieve this

await this.ShowPopupAsync(new SimplePopup(), new PopupOptions
{
    Shape = null
});

Shadow

PopupOptions provides the Shadow property which can control the shadow which is applied to the Popup. The following example shows how to set the shadow to be green with an opacity of 80%.

await this.ShowPopupAsync(new SimplePopup(), new PopupOptions
{
    Shadow = new Shadow
    {
        Brush = Brush.Green,
        Opacity = 0.8f
    }
});

For more details on how to customize the Shadow property see .NET MAUI Shadow.

Disable the Popup Shadow

In order to disable the border on a Popup, simply set the Shape property to null. The following example shows how to achieve this

await this.ShowPopupAsync(new SimplePopup(), new PopupOptions
{
    Shadow = null
});

Passing data to a popup has changed

The original way of passing data through the onPresenting parameter when calling IPopupService.Show no longer exists. This has now been replaced by using the IQueryAttributable interface that integrates with .NET MAUI Shell. You can use it just like you do with .NET MAUI Shell to Process navigation data using a single method. This change was made in order to provide a unified and therefore consistent way of passing navigation data around a .NET MAUI application.

Original way:

public class NamePopupViewModel : ObservableObject
{
    [ObservableProperty]
    public partial string Name { get; set; } = "";
}

public class MyViewModel : INotifyPropertyChanged
{
    private readonly IPopupService popupService;

    public MyViewModel(IPopupService popupService)
    {
        this.popupService = popupService;
    }

    public void DisplayPopup()
    {
        this.popupService.ShowPopup<NamePopupViewModel>(onPresenting: viewModel => viewModel.Name = "Shaun");
    }
}

New way:

public class NamePopupViewModel : ObservableObject, IQueryAttributable
{
    [ObservableProperty]
    public partial string Name { get; set; } = "";

    public void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        Name = query[nameof(NamePopupViewModel.Name)] as string;
    }
}

public class MyViewModel : INotifyPropertyChanged
{
    private readonly IPopupService popupService;

    public MyViewModel(IPopupService popupService)
    {
        this.popupService = popupService;
    }

    public async Task DisplayPopup()
    {
        var queryAttributes = new Dictionary<string, object>
        {
            [nameof(NamePopupViewModel.Name)] = "Shaun"
        };

        await this.popupService.ShowPopupAsync<NamePopupViewModel>(
            Shell.Current,
            options: null,
            queryAttributes);
    }
}

IPopupService methods

All methods (Show and Close) on IPopupService now require a developer to pass in the Shell or INavigation implementation in order to determine which window the popup will be displayed on.

Using INavigation

You can pass in the Shell.Current property in order to provide the current instance. The following example assumes the earlier example of SimplePopup exists within the codebase and uses the Navigation property that exists on the base class of Page provided by .NET MAUI.

public class MyPage : ContentPage
{
    public async Task DisplayPopup()
    {
        await popupService.ShowPopupAsync<SimplePopup>(Navigation);
    }
}

Using Shell

You can pass in the Shell.Current property in order to provide the current instance. The following example assumes the earlier example of SimplePopup exists within the codebase.

await popupService.ShowPopupAsync<SimplePopup>(Shell.Current);

Finishing up

We hope that you enjoy the new functionality!

MAUI Community Toolkit

About

Usage

Migration Guides

Contribution

Development/Testing

Monthly Standups

Core Maintainers

Clone this wiki locally