How to Build a Reusable Multi-Select Dropdown in .NET MAUI with Custom Design π¨π±


When working on mobile apps, providing a seamless and visually appealing multi-select dropdown is essential. In this guide, we'll create a custom multi-select dropdown in .NET MAUI using a modal page. The selected items will be displayed in an Entry-like UI with cards. π
β¨ Features
β Selected items displayed as cards in an Entry-like design. β Multi-select capability with checkboxes. β Smooth user experience with animations. β Custom modal page for item selection. β Fully reusable generic control.
π¨ Designing a Reusable Multi-Select Control
To make our control reusable, we should create a separate custom control that can be used in multiple places without rewriting logic.
π MultiSelection.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentView
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="NewMultiSelect.Controls.MultiSelect.MultiSelection"
x:Name="multiSelect">
<VerticalStackLayout Spacing="5">
<Label x:Name="lblHeading" Style="{StaticResource SmallMediumHorizontalStart}"/>
<Border Stroke="#9CA0B1" StrokeShape="RoundRectangle 7" Padding="9,12">
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="5">
<Label x:Name="lbl" Grid.Column="0" Grid.ColumnSpan="1" Text="Select options" Style="{StaticResource SmallRegularGray}"
IsVisible="{Binding PlaceholderVisibility,Source={x:Reference multiSelect}}" TextColor="#C4C4C4" Margin="0,5"/>
<CollectionView x:Name="collectionView" Grid.Column="0" ItemsSource="{Binding SelectedItems, Source={x:Reference multiSelect}}" HeightRequest="{OnPlatform iOS=30}" IsVisible="{Binding PlaceholderVisibility,Source={x:Reference multiSelect},Converter={StaticResource InvertedBoolConverter}}">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal" ItemSpacing="5"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource VerySmallTransparentBorderOrangeBackgroundRoundRecBorder}" Padding="5,3">
<HorizontalStackLayout Spacing="3">
<Image Source="white_cross" Style="{StaticResource ImageCenterAspectFit}" HeightRequest="13" WidthRequest="13" BindingContext="{Binding .}">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="RemoveItem"/>
</Image.GestureRecognizers>
</Image>
<Label Text="{Binding Value}" Style="{StaticResource TooMicroMediumWhiteHorizontalCenter}" FontSize="13"/>
</HorizontalStackLayout>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Image Grid.Column="1" Source="black_dropdown" Opacity=".5" HeightRequest="15" WidthRequest="15" VerticalOptions="Center" Margin="0,0,7,0"/>
</Grid>
<Border.GestureRecognizers>
<TapGestureRecognizer Tapped="NavigateToMultiSelect"/>
</Border.GestureRecognizers>
</Border>
<Label x:Name="lblError" Text="{Binding Source={x:Reference multiSelect},Path= ErrorText}" IsVisible="{Binding Source={x:Reference multiSelect},Path=IsItemSelected}" Style="{StaticResource MicroSemiboldHorizontalStart}" TextColor="#EE0808" Margin="5,0,0,0"></Label>
</VerticalStackLayout>
</ContentView>
π MultiSelection.xaml.cs
using System.Collections.ObjectModel;
using NewMultiSelect.Models.Registration;
namespace NewMultiSelect.Controls.MultiSelect;
public partial class MultiSelection : ContentView
{
public static readonly BindableProperty PlaceholderVisibilityProperty =
BindableProperty.Create(nameof(PlaceholderVisibility), typeof(bool), typeof(MultiSelection), false, BindingMode.TwoWay);
public static readonly BindableProperty SelectedItemsProperty =
BindableProperty.Create(nameof(SelectedItems), typeof(ObservableCollection<Generic>), typeof(MultiSelection), null, BindingMode.TwoWay);
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create(nameof(ItemsSource), typeof(ObservableCollection<Generic>), typeof(MultiSelection), new ObservableCollection<Generic>(), BindingMode.TwoWay);
public static readonly BindableProperty IsItemSelectedProperty =
BindableProperty.Create(nameof(IsItemSelected), typeof(bool), typeof(MultiSelection), false);
public static readonly BindableProperty ErrorTextProperty =
BindableProperty.Create(nameof(ErrorText), typeof(string), typeof(MultiSelection), default(string));
public string ErrorText
{
get => (string)GetValue(ErrorTextProperty);
set => SetValue(ErrorTextProperty, value);
}
public bool IsItemSelected
{
get => (bool)GetValue(IsItemSelectedProperty);
set => SetValue(IsItemSelectedProperty, value);
}
public bool PlaceholderVisibility
{
get => (bool)GetValue(PlaceholderVisibilityProperty);
set
{
SetValue(PlaceholderVisibilityProperty, value);
}
}
public ObservableCollection<Generic> SelectedItems
{
get => (ObservableCollection<Generic>)GetValue(SelectedItemsProperty);
set
{
var oldCollection = (ObservableCollection<Generic>)GetValue(SelectedItemsProperty);
if (oldCollection != null)
{
oldCollection.CollectionChanged -= OnSelectedItemsChanged;
}
SetValue(SelectedItemsProperty, value);
if (value != null)
{
value.CollectionChanged += OnSelectedItemsChanged;
}
// Update PlaceholderVisibility when the collection is set
UpdatePlaceholderVisibility();
}
}
private void OnSelectedItemsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UpdatePlaceholderVisibility();
}
private void UpdatePlaceholderVisibility()
{
IsItemSelected = PlaceholderVisibility = SelectedItems == null || SelectedItems.Count == 0;
}
public ObservableCollection<Generic> ItemsSource
{
get => (ObservableCollection<Generic>)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public string Heading
{
get { return lblHeading.Text; }
set { lblHeading.Text = value; }
}
public MultiSelection()
{
InitializeComponent();
}
private async void NavigateToMultiSelect(object sender, TappedEventArgs e)
{
// Push the modal for Multi Select
var countrySelectionPage = new MultiSelectionPage(ItemsSource, SelectedItems);
countrySelectionPage.Disappearing+=(s,e)=>
{
// Ensure SelectedItems is updated from the modal page
if (countrySelectionPage.SelectedItems != null && countrySelectionPage.SelectedItems.Count > 0)
{
if(SelectedItems==null)
SelectedItems = new ObservableCollection<Generic>();
SelectedItems = countrySelectionPage.SelectedItems;
}
};
await AppShell.Current.Navigation.PushModalAsync(countrySelectionPage);
}
private void RemoveItem(object sender, TappedEventArgs e)
{
var img = sender as Image;
var item = img.BindingContext as Generic;
SelectedItems.Remove(item);
}
}
π Creating the Multi-Select Modal Page
This modal page will display a list of items with checkboxes, allowing users to select multiple options.
MultiSelectionPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="NewMultiSelect.Controls.MultiSelect.MultiSelectionPage">
<Grid RowDefinitions="Auto,*,Auto" Padding="15" RowSpacing="15">
<ImageButton Grid.Column="0" Source="left_arrow" Style="{StaticResource CircleImageButton}" Clicked="OnBackPressed" HorizontalOptions="StartAndExpand"/>
<!-- Multi Selection -->
<CollectionView x:Name="list"
Grid.Row="1"
SelectionMode="None"
VerticalOptions="FillAndExpand">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" ItemSpacing="5"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource RoundRecBorder}" Padding="10,5,0,5" BindingContext="{Binding .}">
<Grid RowDefinitions="Auto" ColumnDefinitions="*,Auto" Padding="0" VerticalOptions="Center">
<!-- Item Name -->
<Label Text="{Binding Value}" Style="{StaticResource SmallMediumHorizontalStart}" VerticalOptions="Center"/>
<!-- Multi Select Checkbox -->
<CheckBox Grid.Row="0" Grid.Column="1" VerticalOptions="Center" HorizontalOptions="Center" IsChecked="{Binding IsChecked}" IsEnabled="False" Color="{StaticResource Primary}"/>
</Grid>
<Border.GestureRecognizers>
<TapGestureRecognizer Tapped="MultiSelectItem"/>
</Border.GestureRecognizers>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Button x:Name="btnSubmit" Grid.Row="2" Text="Submit" Style="{StaticResource PaidMealsButton}"/>
</Grid>
</ContentPage>
MultiSelectionPage.xaml.cs
using System.Collections.ObjectModel;
using NewMultiSelect.Models.Generic;
namespace NewMultiSelect.Controls.MultiSelect;
public partial class MultiSelectionPage : ContentPage
{
public ObservableCollection<Generic> SelectedItems {get;set;}
public MultiSelectionPage(ObservableCollection<Generic> itemsSource, ObservableCollection<Generic> selectedItems)
{
InitializeComponent();
btnSubmit.Clicked+= async(s,e)=>
{
await AppShell.Current.Navigation.PopModalAsync();
};
SelectedItems = selectedItems ?? new ObservableCollection<Generic>();
var selectedIds = SelectedItems.Select(x => x.Key).ToHashSet();
itemsSource.ToList().ForEach(item => item.IsChecked = selectedIds?.Contains(item.Key));
list.ItemsSource =itemsSource;
}
private async void OnBackPressed(System.Object sender, System.EventArgs e)
{
await AppShell.Current.Navigation.PopModalAsync();
}
private void MultiSelectItem(object sender, TappedEventArgs e)
{
var border = sender as Border;
var item = border.BindingContext as Generic;
if(item.IsChecked.Equals(true))
{
item.IsChecked =false;
SelectedItems.Remove(item);
}
else
{
item.IsChecked =true;
SelectedItems.Add(item);
}
}
}
π Integrating the Multi-Select Control in a Page
Now that we have the custom control, let's integrate it into a page! π
Mainpage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="NewMultiSelect.Views.MainPage"
xmlns:controls="clr-namespace:PaidMeals.Controls">
<VerticalStackLayout>
<multiSelect:MultiSelection Heading="Dietary Options" PlaceholderVisibility="True"
ItemsSource="{Binding MenuItemForm.DiatoryOptions}" SelectedItems="{Binding MenuItemForm.SelectedDiatoryOptions.Value}"
ErrorText="{Binding MenuItemForm.SelectedDiatoryOptions.Error,Mode=TwoWay}"
IsItemSelected="{Binding MenuItemForm.SelectedDiatoryOptions.IsInvalid,Mode=TwoWay}"/>
</VerticalStackLayout>
</ContentPage>
π Best Practices for Reusable Controls π―
To make your own controls truly reusable, follow these best practices:
1οΈβ£ Encapsulation & Reusability
Create a separate custom control (
MultiSelectControl
) to avoid code duplication.Use bindable properties to pass data dynamically.
2οΈβ£ Generic Data Handling
Instead of hardcoding item lists, allow the control to accept any data type by using generic data binding.
Use commands and events to communicate with the parent view.
3οΈβ£ Separation of Concerns
Keep UI logic inside XAML and business logic in the code-behind or ViewModel.
Ensure MVVM compatibility for better state management.
4οΈβ£ Scalability & Performance
Use OnProprtyChange for real-time UI updates.
Optimize performance by using collectionview.
5οΈβ£ Customizability & Theming
- Allow users to customize UI properties like background color, text size, icons, etc.
π Wrapping Up
Now, we have a fully reusable multi-select dropdown control in .NET MAUI! π The users can select multiple items, which are displayed as cards inside an Entry-like UI.
π₯ Key Takeaways:
Created a Separate Control for better reusability.
Used Bindable Properties to allow flexibility.
Followed Best Practices to make the control scalable and maintainable.
You can now use this generic multi-select control anywhere in your app! π
Happy coding! π₯
Subscribe to my newsletter
Read articles from Ali Raza directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Ali Raza
Ali Raza
π Tech Lead | .NET MAUI Expert | Mobile App Developer I'm Ali Raza, a passionate Tech Lead with over 6 years of experience in mobile app development. I specialize in .NET MAUI/Xamarin and have led multiple high-impact projects, including enterprise apps, fintech solutions, and eSIM technology. πΉ What I Do: β .NET MAUI & Xamarin β Building cross-platform apps with robust architectures β eSIM & Fintech Apps β Leading innovations in digital connectivity & finance β Performance Optimization β Creating high-quality, scalable mobile solutions β Mentorship & Community Sharing β Helping developers master .NET MAUI π’ Sharing Weekly Insights on .NET MAUI/Xamarin to help developers level up! Follow me for deep dives into Native Interop, API Optimization, and Advanced UI/UX techniques. Letβs connect and build something amazing! π