Are you still writing the same XAML over and over for your app’s UI? What if I told you that one powerful .NET MAUI feature could save you dozens of hours while keeping your UI clean, consistent, and scalable? Let’s dive deep into creating custom controls in .NET MAUI and learn how to build polished, reusable UI components that feel native on every platform.
Understanding Custom Controls in .NET MAUI
What Are Custom Controls?
Custom controls are UI elements you define yourself, from scratch, often with encapsulated behavior, appearance, and logic. Unlike user controls (which are more like reusable UI composites) or templates (which define look-and-feel), custom controls extend the core rendering pipeline and integrate deeply into the framework.
In short:
- Custom Controls = new UI components built from scratch.
- User Controls = compositions of existing controls.
- Templates = visual customization of existing controls.
Why Use Custom Controls?
Use custom controls when:
- You need cross-platform, reusable components.
- Your logic/UI is repeated in many places.
- You want to expose properties for easy binding (MVVM).
- You need full control over rendering and platform behavior.
For example, a rating star component, color picker, or notification banner might be ideal as a custom control.
Key Components of a MAUI Custom Control
To build a full-featured MAUI control, you’ll often use:
- Handlers: Bridge between your control and the native platforms.
- Renderers (legacy, pre-MAUI): Used in Xamarin.Forms.
- Bindable Properties: To support data binding and MVVM.
- Styling & Visual States: For theme and state management.
Building Your First Custom Control
Planning the Control
Before coding, define:
- Purpose: What problem does this control solve?
- Design: How should it look and behave?
- Usage: How will other developers use it?
Let’s build a CustomBadge
control to display labeled badges with optional icons.
Creating the Control Class
public class CustomBadge : GraphicsView
{
public CustomBadge()
{
Drawable = new BadgeDrawable(this);
}
}
This class inherits GraphicsView
, perfect for custom drawing. We assign a custom BadgeDrawable
to handle rendering.
Adding Bindable Properties
public static readonly BindableProperty TextProperty =
BindableProperty.Create(nameof(Text), typeof(string), typeof(CustomBadge), string.Empty);
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
Now Text
can be data-bound from your ViewModel, which is essential for MVVM.
Customizing Appearance and Behavior
In BadgeDrawable
:
public class BadgeDrawable : IDrawable
{
private readonly CustomBadge _badge;
public BadgeDrawable(CustomBadge badge) => _badge = badge;
public void Draw(ICanvas canvas, RectF dirtyRect)
{
canvas.FillColor = Colors.CornflowerBlue;
canvas.FillRoundedRectangle(dirtyRect, 8);
canvas.FontSize = 14;
canvas.FontColor = Colors.White;
canvas.DrawString(_badge.Text, dirtyRect, HorizontalAlignment.Center, VerticalAlignment.Center);
}
}
This paints a rounded badge with text centered in the middle.
Advanced Custom Control Techniques
Composing Controls Using XAML
Sometimes it’s easier to define visuals in XAML:
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
x:Class="MyControls.FancyButton">
<Frame BackgroundColor="LightGray" CornerRadius="12">
<Label x:Name="buttonLabel" Text="Click Me!" HorizontalOptions="Center" VerticalOptions="Center"/>
</Frame>
</ContentView>
This is useful for composite visuals but can expose bindable properties too.
Creating Platform-Specific Customizations
Add platform logic using partial classes or conditional compilation:
#if ANDROID
// Android-specific behavior
#elif IOS
// iOS-specific behavior
#endif
Or register handlers:
#if ANDROID
Microsoft.Maui.Handlers.ButtonHandler.Mapper.AppendToMapping("MyCustom", (handler, view) =>
{
// custom behavior
});
#endif
Performance Considerations
- Use
GraphicsView
instead ofSkiaSharp
for simpler needs. - Avoid layout thrashing: minimize
InvalidateMeasure()
. - Debounce property change notifications.
Reusability and Distribution
Packaging Controls into Reusable Libraries
- Use
.NET MAUI Class Library
project. - Isolate platform-specific parts using folders: Platforms/Android, Platforms/iOS, etc.
- Use
InternalsVisibleTo
for internal sharing between test projects.
Publishing to NuGet
Steps:
- Add metadata in
.csproj
. - Use
dotnet pack
to generate.nupkg
. - Push to NuGet using
dotnet nuget push
.
Versioning and Documentation Best Practices
- Follow Semantic Versioning.
- Use XML comments and DocFX for documentation.
- Create sample projects to demonstrate usage.
Real-World Examples and Use Cases
Custom Slider with Icons
Imagine a volume slider where each stop has an icon:
- Combine a
Slider
with custom tick rendering. - Add icons using
AbsoluteLayout
.
CustomSliderWithIcons.xaml
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
x:Class="CustomControls.CustomSliderWithIcons">
<Grid>
<Slider x:Name="SliderControl"
Minimum="0"
Maximum="4"
ValueChanged="Slider_ValueChanged" />
<Grid ColumnDefinitions="*,*,*,*,*"
VerticalOptions="Center"
HorizontalOptions="FillAndExpand"
InputTransparent="True">
<Image Source="icon1.png" Grid.Column="0" HeightRequest="24" WidthRequest="24" HorizontalOptions="Center" />
<Image Source="icon2.png" Grid.Column="1" HeightRequest="24" WidthRequest="24" HorizontalOptions="Center" />
<Image Source="icon3.png" Grid.Column="2" HeightRequest="24" WidthRequest="24" HorizontalOptions="Center" />
<Image Source="icon4.png" Grid.Column="3" HeightRequest="24" WidthRequest="24" HorizontalOptions="Center" />
<Image Source="icon5.png" Grid.Column="4" HeightRequest="24" WidthRequest="24" HorizontalOptions="Center" />
</Grid>
</Grid>
</ContentView>
CustomSliderWithIcons.xaml.cs
public partial class CustomSliderWithIcons : ContentView
{
public CustomSliderWithIcons()
{
InitializeComponent();
}
private void Slider_ValueChanged(object sender, ValueChangedEventArgs e)
{
// Respond to slider changes here
Console.WriteLine($"Slider value: {e.NewValue}");
}
}
Calendar Control with Custom Events
Use CollectionView
to render dates:
- Bind each cell to a date object.
- Highlight events using background color or badges.
EventCalendar.xaml
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
x:Class="CustomControls.EventCalendar">
<CollectionView x:Name="CalendarView"
ItemsLayout="UniformGrid, 7"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame Padding="5" Margin="2" BackgroundColor="{Binding EventColor}">
<Label Text="{Binding Date}" FontSize="12" HorizontalOptions="Center" />
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentView>
EventCalendar.xaml.cs
public partial class EventCalendar : ContentView
{
public ObservableCollection<CalendarDate> Dates { get; set; } = new();
public EventCalendar()
{
InitializeComponent();
CalendarView.ItemsSource = Dates;
}
}
public class CalendarDate
{
public string Date { get; set; }
public Color EventColor { get; set; } = Colors.Transparent;
}
Reusable Notification Banner
Build a banner that:
- Animates in/out.
- Binds to a message queue.
- Uses
TranslateTo
andFadeTo
for transitions.
NotificationBanner.xaml
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
x:Class="CustomControls.NotificationBanner"
IsVisible="False">
<Frame BackgroundColor="DodgerBlue"
Padding="10"
CornerRadius="6"
HorizontalOptions="FillAndExpand">
<Label x:Name="MessageLabel" TextColor="White" />
</Frame>
</ContentView>
NotificationBanner.xaml.cs
public partial class NotificationBanner : ContentView
{
public NotificationBanner()
{
InitializeComponent();
}
public async Task ShowMessageAsync(string message, int duration = 3000)
{
MessageLabel.Text = message;
IsVisible = true;
await this.FadeTo(1).ContinueWith(async _ =>
{
await Task.Delay(duration);
await this.FadeTo(0);
IsVisible = false;
});
}
}
Use this with a view model or message queue to show transient notifications on screen.
FAQ: Common Questions on MAUI Custom Controls
Use custom controls for deep customization or new components. Use user controls for combining existing ones.
Yes, especially when you inherit from ContentView
or Layout
.
Override touch handlers or use GestureRecognizer
depending on control base class.
Conclusion: Make Your UI Smarter, Not Harder
Creating custom controls in .NET MAUI is like building your own LEGO pieces for cross-platform apps. They save time, enforce consistency, and open up endless customization. So what are you waiting for? Start building smarter UI blocks today and level up your MAUI development game.
Have you tried building a custom control? Share your experience or ask your questions in the comments!