π Show native BottomSheets with .NET MAUI!
- π§ Built-in NavigationService - Seamless navigation between bottom sheets
- π Flexible Content - Open any ContentPage or View as BottomSheet
- π¨ Customizable Layout - Create BottomSheets in any layout
- π Configurable Header - Full header customization support
- ποΈ MVVM Support - Complete MVVM pattern integration
- π― Native Performance - Platform-specific implementations for optimal UX
Check out the sample project to see the API in action!
iPhone
iPad
|
Phone
Tablet
|
|
|
Install the NuGet package:
dotnet add package Plugin.Maui.BottomSheetEnable the plugin in your MauiProgram.cs:
using Plugin.Maui.BottomSheet.Hosting;
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseBottomSheet() // π Add this line
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();Add the XAML namespace:
xmlns:bottomsheet="http://pluginmauibottomsheet.com"
Create a simple bottom sheet:
<bottomsheet:BottomSheet x:Name="MyBottomSheet" IsOpen="{Binding IsSheetOpen}">
<bottomsheet:BottomSheetContent>
<Label Text="Hello from Bottom Sheet!" />
</bottomsheet:BottomSheetContent>
</bottomsheet:BottomSheet>Set peek height to view height (peek height changes dynamically as view height changes)
<mauibottomsheet:BottomSheet
PeekHeight="{mauibottomsheet:PeekView View={x:Reference ContentBox}}"
States="Peek">
<mauibottomsheet:BottomSheetContent>
<mauibottomsheet:BottomSheetContent.Content>
<Grid RowDefinitions="300">
<BoxView x:Name="ContentBox" Color="Orange" />
</Grid>
</mauibottomsheet:BottomSheetContent.Content>
</mauibottomsheet:BottomSheetContent>
</mauibottomsheet:BottomSheet>or
<mauibottomsheet:BottomSheet
PeekHeight="{mauibottomsheet:PeekView ContentBox}"
States="Peek">
<mauibottomsheet:BottomSheetContent>
<mauibottomsheet:BottomSheetContent.ContentTemplate>
<DataTemplate>
<Grid RowDefinitions="300">
<BoxView x:Name="ContentBox" Color="Orange" />
</Grid>
</DataTemplate>
</mauibottomsheet:BottomSheetContent.ContentTemplate>
</mauibottomsheet:BottomSheetContent>
</mauibottomsheet:BottomSheet>Caution
The peek height is calculated using MAUI's measurement behavior. Different platforms may return different heights for the same layout. You should not set a ScrollView as your peek view reference. Use the root of your ScrollView instead. This is especially true when using VerticalStackLayout and Grid with Star rows. To observe the differences, launch the Plugin.BottomSheet.Tests.Maui.Ui.Application test project and open the various peek BottomSheets
| Property | Type | Description |
|---|---|---|
IsModal |
bool |
Enable/disable interaction with content under BottomSheet |
IsCancelable |
bool |
Allow user to close via gestures or background click |
HasHandle |
bool |
Show/hide the drag handle |
ShowHeader |
bool |
Show/hide the header section |
IsOpen |
bool |
Control open/close state |
IsDraggable |
bool |
Enable/disable drag gestures (useful for drawing) |
States |
List<BottomSheetState> |
Allowed states (Peek, Medium, Large) |
CurrentState |
BottomSheetState |
Current display state |
PeekHeight |
double |
Height when in peek state (iOS 16+) |
Padding |
double |
Internal padding |
BackgroundColor |
Color |
Background color |
CornerRadius |
float |
Top corner radius |
WindowBackgroundColor |
Color |
Window background (modal only) |
SizeMode |
BottomSheetSizeMode |
Represents a size mode for the bottom sheet where the height is adjusted dynamically to fit the content displayed within it or a fixed value based on the number of states it can display. |
| State | Description |
|---|---|
States |
Represents a size mode for the bottom sheet where the height is set to a fixed value based on the number of states it can display. This mode ensures the bottom sheet is fully expanded rather than dynamically adjusting its size to-fit content. |
FitToContent |
A size mode indicating that the bottom sheet dynamically adjusts its height to precisely fit the content it displays. In this mode, the bottom sheet wraps around its contents and does not occupy any more vertical space than necessary. |
Caution
Mode FitToContent is not compatible with the States and CurrentState property. Do not mix these two properties.
Requires iOS 16+. On prior versions of iOS, the medium state will be used instead.
| State | Description |
|---|---|
Peek |
Fractional height (customizable) |
Medium |
Half screen height |
Large |
Full screen height |
| Property | Type | Description |
|---|---|---|
TitleText |
string |
Title text displayed in header |
TopLeftButton |
Button |
Custom button for top-left position |
TopRightButton |
Button |
Custom button for top-right position |
HeaderDataTemplate |
DataTemplate |
Custom view template (overrides other header properties) |
Content |
View |
Direct view content |
HeaderAppearance |
BottomSheetHeaderButtonAppearanceMode |
Controls which buttons are displayed |
ShowCloseButton |
bool |
Show built-in close button |
CloseButtonPosition |
CloseButtonPosition |
Position of close button (Left/Right) |
Caution
Be careful when using Content because the Content will be created even if the BottomSheet isn't open and this may have a negative performance impact.
Content should only be used with navigation and not in BottomSheets added directly to a Layout.
| Property | Type | Description |
|---|---|---|
Content |
View |
Direct view content |
ContentTemplate |
DataTemplate |
Template for lazy loading |
Caution
Be careful when using Content because the Content will be created even if the BottomSheet isn't open and this may have a negative performance impact.
Content should only be used with navigation and not in BottomSheets added directly to a Layout.
| Property | Type | Description |
|---|---|---|
HeaderStyle |
BottomSheetHeaderStyle |
Styling configuration for header elements |
| Property | Type | Description |
|---|---|---|
TitleTextColor |
Color |
Color of the title text |
TitleTextFontSize |
double |
Font size of the title text |
TitleTextFontAttributes |
FontAttributes |
Font attributes (Bold, Italic, None) |
TitleTextFontFamily |
string |
Font family name for title text |
TitleTextFontAutoScalingEnabled |
bool |
Enable automatic font scaling |
CloseButtonHeightRequest |
double |
Requested height for close button |
CloseButtonWidthRequest |
double |
Requested width for close button |
CloseButtonTintColor |
Color |
Tint color for close button icon |
<bottomsheet:BottomSheet
x:Name="AdvancedBottomSheet"
Padding="20"
CornerRadius="20"
HasHandle="True"
IsCancelable="True"
IsDraggable="True"
IsModal="True"
IsOpen="{Binding IsOpen}"
ShowHeader="True"
States="Peek,Medium,Large"
CurrentState="Medium"
WindowBackgroundColor="Black">
<!-- Header Configuration -->
<bottomsheet:BottomSheet.Header>
<bottomsheet:BottomSheetHeader
CloseButtonPosition="Right"
HeaderAppearance="LeftAndRightButton"
ShowCloseButton="True"
TitleText="My Awesome Sheet">
<bottomsheet:BottomSheetHeader.TopLeftButton>
<Button Command="{Binding LeftButtonCommand}" Text="Cancel" />
</bottomsheet:BottomSheetHeader.TopLeftButton>
<bottomsheet:BottomSheetHeader.TopRightButton>
<Button Command="{Binding RightButtonCommand}" Text="Save" />
</bottomsheet:BottomSheetHeader.TopRightButton>
</bottomsheet:BottomSheetHeader>
</bottomsheet:BottomSheet.Header>
<!-- Content with Peek Height Behavior -->
<bottomsheet:BottomSheetContent>
<bottomsheet:BottomSheetContent.ContentTemplate>
<DataTemplate>
<VerticalStackLayout>
<!-- Peek Content -->
<ContentView>
<ContentView.Behaviors>
<bottomsheet:BottomSheetPeekBehavior />
</ContentView.Behaviors>
<Label Text="This content is visible in peek mode" />
</ContentView>
<!-- Full Content -->
<Grid>
<Label Text="This content appears when expanded" />
<!-- Your content here -->
</Grid>
</VerticalStackLayout>
</DataTemplate>
</bottomsheet:BottomSheetContent.ContentTemplate>
</bottomsheet:BottomSheetContent>
<!-- Styling -->
<bottomsheet:BottomSheet.BottomSheetStyle>
<bottomsheet:BottomSheetStyle>
<bottomsheet:BottomSheetStyle.HeaderStyle>
<bottomsheet:BottomSheetHeaderStyle
TitleTextColor="Blue"
TitleTextFontSize="18"
TitleTextFontAttributes="Bold"
CloseButtonTintColor="Red"/>
</bottomsheet:BottomSheetStyle.HeaderStyle>
</bottomsheet:BottomSheetStyle>
</bottomsheet:BottomSheet.BottomSheetStyle>
</bottomsheet:BottomSheet><mauibottomsheet:BottomSheet
SizeMode="FitToContent">
<mauibottomsheet:BottomSheetContent>
<mauibottomsheet:BottomSheetContent.Content>
<Grid RowDefinitions="300">
<BoxView x:Name="ContentBox" Color="Orange" />
</Grid>
</mauibottomsheet:BottomSheetContent.Content>
</mauibottomsheet:BottomSheetContent>
</mauibottomsheet:BottomSheet><Style TargetType="bottomsheet:BottomSheet">
<Setter Property="BottomSheetStyle">
<Setter.Value>
<bottomsheet:BottomSheetStyle>
<bottomsheet:BottomSheetStyle.HeaderStyle>
<bottomsheet:BottomSheetHeaderStyle
CloseButtonHeightRequest="40"
CloseButtonTintColor="LightBlue"
CloseButtonWidthRequest="40"
TitleTextColor="Blue"
TitleTextFontAttributes="Bold"
TitleTextFontAutoScalingEnabled="True"
TitleTextFontFamily="OpenSansRegular"
TitleTextFontSize="20" />
</bottomsheet:BottomSheetStyle.HeaderStyle>
</bottomsheet:BottomSheetStyle>
</Setter.Value>
</Setter>
</Style>Register your bottom sheets for navigation:
// Basic registration
builder.Services.AddBottomSheet<UserProfilePage>("UserProfile");
// With ViewModel binding
builder.Services.AddBottomSheet<UserProfileSheet, UserProfileViewModel>("UserProfile");
// With default configuration
builder.Services.AddBottomSheet<UserProfilePage>("UserProfile", (sheet, page) =>
{
sheet.States = [BottomSheetState.Medium, BottomSheetState.Large];
sheet.CurrentState = BottomSheetState.Large;
sheet.ShowHeader = true;
sheet.Header = new BottomSheetHeader()
{
TitleText = page.Title,
};
});public class MainViewModel
{
private readonly IBottomSheetNavigationService _navigationService;
public MainViewModel(IBottomSheetNavigationService navigationService)
{
_navigationService = navigationService;
}
// Navigate to a bottom sheet
public async Task ShowUserProfile()
{
await _navigationService.NavigateToAsync("UserProfile");
}
// Navigate with parameters
public async Task ShowUserProfile(int userId)
{
var parameters = new BottomSheetNavigationParameters
{
{ "UserId", userId },
{ "Mode", "Edit" },
{ "ShowActions", true }
};
await _navigationService.NavigateToAsync("UserProfile", parameters);
}
// Navigate with parameters and custom configuration
public async Task ShowProductDetails(Product product)
{
var parameters = new BottomSheetNavigationParameters
{
{ "Product", product },
{ "Category", product.CategoryId },
{ "ReadOnly", false }
};
await _navigationService.NavigateToAsync("ProductDetails", parameters, configure: sheet =>
{
sheet.Header.TitleText = product.Name;
sheet.CurrentState = BottomSheetState.Large;
sheet.HasHandle = true;
});
}
// Navigate with ViewModel and parameters
public async Task ShowEditForm<T>(T model, string formType)
{
var parameters = new BottomSheetNavigationParameters
{
{ "Model", model },
{ "FormType", formType },
{ "Timestamp", DateTime.Now }
};
await _navigationService.NavigateToAsync<EditFormViewModel>("EditForm", parameters);
}
// Navigate with complex object parameters
public async Task ShowOrderSummary(Order order, List<OrderItem> items, decimal total)
{
var parameters = new BottomSheetNavigationParameters
{
{ "Order", order },
{ "OrderItems", items },
{ "Total", total },
{ "Currency", "USD" },
{ "CanEdit", order.Status == OrderStatus.Draft }
};
await _navigationService.NavigateToAsync("OrderSummary", parameters);
}
// Go back with result parameters
public async Task CloseWithResult()
{
var resultParameters = new BottomSheetNavigationParameters
{
{ "Result", "Success" },
{ "ModifiedData", GetModifiedData() },
{ "HasChanges", true }
};
await _navigationService.GoBackAsync(resultParameters);
}
// Clear all sheets with notification parameters
public async Task ClearBottomSheetStackAsync()
{
var parameters = new BottomSheetNavigationParameters
{
{ "CloseReason", "ForceClose" },
{ "SaveState", true }
};
await _navigationService.ClearBottomSheetStack(parameters);
}
}Implement INavigationAware for lifecycle events:
public class UserProfileViewModel : INavigationAware
{
public void OnNavigatedTo(IBottomSheetNavigationParameters parameters)
{
// Sheet opened
var userId = parameters.GetValue<string>("UserId");
LoadUserData(userId);
}
public void OnNavigatedFrom(IBottomSheetNavigationParameters parameters)
{
// Sheet closed or another sheet opened
SaveChanges();
}
}Tip
Not only can the BottomSheetβs ViewModel implement INavigationAware, but so can the ViewModel of the parent view that launched the BottomSheet. This allows the parent to respond when:
- The first BottomSheet is opened (OnNavigatedFrom)
- The last BottomSheet is closed (OnNavigatedTo)
This is especially useful for suspending or restoring state in the parent view while modals are active.
Implement confirmation dialogs:
public class EditUserViewModel : IConfirmNavigationAsync
{
public async Task<bool> CanNavigateAsync(IBottomSheetNavigationParameters? parameters)
{
if (HasUnsavedChanges)
{
return await Shell.Current.CurrentPage.DisplayAlert(
"Unsaved Changes",
"You have unsaved changes. Do you want to discard them?",
"Discard",
"Cancel");
}
return true;
}
}Important
When using the IsOpen to manage the visibility of a BottomSheet, the parent ViewModel can implement INavigationAware to respond to the lifecycle of the BottomSheet.
This allows the parent ViewModel to handle actions when:
| Scenario | INavigationAware on Parent Triggered? |
|---|---|
| BottomSheet opened via navigation | β No |
| BottomSheet opened without navigation (IsOpen) | β Yes |
public class MyViewModel
{
public ICommand TopLeftButtonCommand { get; }
public ICommand TopRightButtonCommand { get; }
public ICommand OpeningCommand { get; }
public ICommand OpenedCommand { get; }
public ICommand ClosingCommand { get; }
public ICommand ClosedCommand { get; }
}MyBottomSheet.Opening += OnOpening;
MyBottomSheet.Opened += OnOpened;
MyBottomSheet.Closing += OnClosing;
MyBottomSheet.Closed += OnClosed;// Set custom theme before opening
MyBottomSheet.On<Android>().SetTheme(Resource.Style.My_Awesome_BottomSheetDialog);// Code approach
MyBottomSheet.On<Android>().SetMaxHeight(800);
MyBottomSheet.On<Android>().SetMaxWidth(600);
MyBottomSheet.On<Android>().SetMargin(new Thickness(10, 0, 10, 0));
MyBottomSheet.On<Android>().SetHalfExpanedRatio = 0.8f;
MyBottomSheet.On<Android>().SetShouldRemoveExpandedCorners = false;<!-- XAML approach -->
xmlns:androidBottomsheet="http://pluginmauibottomsheet.com/platformconfiguration/android"
<bottomsheet:BottomSheet
androidBottomsheet:BottomSheet.MaxWidth="300"
androidBottomsheet:BottomSheet.Margin="10,0,10,0"
androidBottomsheet:BottomSheet.HalfExpandedRatio="0.8"
androidBottomsheet:BottomSheet.ShouldRemoveExpandedCorners="false">
<!-- Content -->
</bottomsheet:BottomSheet>EdgeToEdge support is built-in and enabled by default.
If you create your own theme make sure to derive from ThemeOverlay.MaterialComponents.BottomSheetDialog and that navigationBarColor is translucent.
To disable:
<item name="enableEdgeToEdge">false</item>By design, sheets are always modal on macOS.
To ensure the same user experience (UX) across desktop applications, Windows sheets are always modal too.
The sheet implementation mimics Microsoft.UI.Xaml.Controls.ContentDialog therefore some default properties are set.
| Property | ResourceKey | Value |
|---|---|---|
| BorderThickness | ContentDialogBorderWidth | 1 |
| MinWidth | ContentDialogMinWidth | 320 |
| MinHeight | ContentDialogMinHeight | 184 |
| MaxWidth | ContentDialogMaxWidth | 548 |
| MaxHeight | ContentDialogMaxHeight | 756 |
// Code approach
MyBottomSheet.On<Windows>().SetMaxHeight(800);
MyBottomSheet.On<Windows>().SetMaxWidth(600);
MyBottomSheet.On<Windows>().SetMinHeight(800);
MyBottomSheet.On<Windows>().SetMinWidth(600);<!-- XAML approach -->
xmlns:windowsBottomsheet="http://pluginmauibottomsheet.com/platformconfiguration/windows"
<bottomsheet:BottomSheet
androidBottomsheet:BottomSheet.MinWidth="300"
androidBottomsheet:BottomSheet.MinHeight="300"
androidBottomsheet:BottomSheet.MaxWidth="300"
androidBottomsheet:BottomSheet.MaxHeight="300">
<!-- Content -->
</bottomsheet:BottomSheet>| Feature | iOS | Android | MacCatalyst | Windows |
|---|---|---|---|---|
| Edge-to-Edge | β | β | β | β |
| PeekHeight | iOS 16+ | β | β | β |
| Custom Themes | β | β | β | β |
| Modal Only | β | β | β | β |
using Microsoft.Maui.LifecycleEvents;
using Plugin.Maui.BottomSheet.LifecycleEvents;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseBottomSheet();
#if ANDROID
builder.ConfigureLifecycleEvents(events =>
{
events.AddAndroid(android =>
{
android.OnBottomSheetBackPressed(activity =>
{
// Handle back button press
Debug.WriteLine("BottomSheet back button pressed");
});
});
});
#endif
return builder.Build();
}
}- Use
ContentTemplateinstead ofContentfor better performance - Implement lazy loading for complex content
- Consider using
INavigationAwarefor proper lifecycle management
- Don't mix
IsOpenwith navigation methods - Always implement
IConfirmNavigationfor forms with user input
- XAML and .NET HotReload are not supported for
BottomSheet - If you face any issues please open an issue and describe your setup(Rider, VSCode, Visual Studio, Platform, OS etc.)
This project is licensed under the MIT License - see the LICENSE file for details.
- π Bug Reports
- π¬ Discussions




