Creating Custom Controls in .NET MAUI — A Step-by-Step Guide

·

·


🧭 Introduction

When building cross-platform apps with .NET MAUI (Multi-platform App UI), you’ll often find yourself repeating UI patterns — like custom buttons, input fields, or layouts — across multiple pages.
That’s where custom controls come in.

Custom controls in .NET MAUI let you:

  • Reuse UI logic and layout across pages and projects
  • Simplify maintenance
  • Improve code readability and consistency

In this tutorial, we’ll explore how to create, use, and style a custom control in .NET MAUI — step by step.


🛠️ Prerequisites

Before we start, make sure you have:

  • .NET 8 SDK (or later)
  • Visual Studio 2022 (17.8 or newer)
  • A .NET MAUI App template created (dotnet new maui -n MauiCustomControlDemo)

🚀 Step 1: Understanding the Types of Custom Controls

In .NET MAUI, you can create custom controls in three main ways:

  1. User Controls (Composable Controls) – built using XAML and C#, perfect for combining existing controls.
  2. Templated Controls – fully customizable look & feel, defined by ControlTemplates.
  3. Drawn Controls (Graphics) – for creating highly customized visuals using Microsoft.Maui.Graphics.

In this tutorial, we’ll start with the most common type — a User Control.


🎨 Step 2: Create a Custom User Control

Let’s create a custom control called RoundedButton.

🧩 Step 2.1: Add a New ContentView

In Visual Studio, right-click on your project →
Add → New Item → .NET MAUI → ContentView (XAML)

Name it:

RoundedButton.xaml

You’ll get two files:

  • RoundedButton.xaml
  • RoundedButton.xaml.cs

🧱 Step 2.2: Define the UI (XAML)

Open RoundedButton.xaml and replace its content with:

<?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="MauiCustomControlDemo.Controls.RoundedButton">

    <Frame 
        Padding="10"
        CornerRadius="25"
        BackgroundColor="{Binding BackgroundColor, Source={x:Reference Root}}"
        HasShadow="True">
        <Label 
            Text="{Binding Text, Source={x:Reference Root}}"
            TextColor="{Binding TextColor, Source={x:Reference Root}}"
            HorizontalOptions="Center"
            VerticalOptions="Center" />
    </Frame>
</ContentView>

Here, we used a Frame with a rounded corner radius, and a Label to show button text.


⚙️ Step 2.3: Add Properties (Code-behind)

Now open RoundedButton.xaml.cs and add the following:

using Microsoft.Maui.Controls;

namespace MauiCustomControlDemo.Controls;

public partial class RoundedButton : ContentView
{
    public static readonly BindableProperty TextProperty =
        BindableProperty.Create(nameof(Text), typeof(string), typeof(RoundedButton), default(string));

    public static readonly BindableProperty TextColorProperty =
        BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(RoundedButton), Colors.White);

    public static readonly BindableProperty BackgroundColorProperty =
        BindableProperty.Create(nameof(BackgroundColor), typeof(Color), typeof(RoundedButton), Colors.Blue);

    public event EventHandler? Clicked;

    public RoundedButton()
    {
        InitializeComponent();

        // Add gesture recognizer for click
        var tapGesture = new TapGestureRecognizer();
        tapGesture.Tapped += (s, e) => Clicked?.Invoke(this, EventArgs.Empty);
        GestureRecognizers.Add(tapGesture);
    }

    public string Text
    {
        get => (string)GetValue(TextProperty);
        set => SetValue(TextProperty, value);
    }

    public Color TextColor
    {
        get => (Color)GetValue(TextColorProperty);
        set => SetValue(TextColorProperty, value);
    }

    public new Color BackgroundColor
    {
        get => (Color)GetValue(BackgroundColorProperty);
        set => SetValue(BackgroundColorProperty, value);
    }
}

This creates bindable properties for Text, TextColor, and BackgroundColor, plus a click event.


🧩 Step 3: Use the Custom Control in a Page

Open MainPage.xaml and use your control like this:

<ContentPage 
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:controls="clr-namespace:MauiCustomControlDemo.Controls"
    x:Class="MauiCustomControlDemo.MainPage">

    <VerticalStackLayout Padding="30" Spacing="20">

        <controls:RoundedButton 
            Text="Tap Me!"
            BackgroundColor="MediumPurple"
            TextColor="White"
            Clicked="OnRoundedButtonClicked" />

    </VerticalStackLayout>
</ContentPage>

And handle the click in MainPage.xaml.cs:

private void OnRoundedButtonClicked(object sender, EventArgs e)
{
    DisplayAlert("Clicked!", "You tapped the custom button!", "OK");
}

🧠 Step 4: Styling the Control (Optional)

You can easily apply a Style in Resources/Styles.xaml:

<Style TargetType="controls:RoundedButton">
    <Setter Property="BackgroundColor" Value="Teal" />
    <Setter Property="TextColor" Value="White" />
</Style>

This keeps your custom controls consistent across your app.


🪄 Step 5: Going Further — Create a Templated Control

If you want a more flexible and reusable control that users can restyle completely, you can create a Templated Control by inheriting from TemplatedView instead of ContentView.

You’d define a ControlTemplate that consumers can override — perfect for creating components like custom entries, toggle buttons, or complex cards.


🎯 Conclusion

You’ve just built your first custom control in .NET MAUI!
Now you can reuse this control across your entire app — and even publish it as a NuGet package for others to use.

Key Takeaways:

  • Use ContentView for simple composable controls.
  • Use TemplatedView for advanced, skinnable controls.
  • Bindable properties make your controls reusable and powerful.

💡 What’s Next?

  • Try adding animation when the button is tapped.
  • Create a custom input control (like a search bar).
  • Package your control into a .NET MAUI library project.


Leave a Reply

Your email address will not be published. Required fields are marked *