Creating Custom Controls in .NET MAUI

Custom controls let you encapsulate UI logic, reuse components across pages, and provide a consistent look and feel. This tutorial guides you through building a reusable RatingView control.

Step 1 – Define the Control

Create a new class that inherits from ContentView. Add bindable properties for the rating value and the maximum number of stars.

using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;

namespace MauiApp.Controls
{
    public class RatingView : ContentView
    {
        public static readonly BindableProperty RatingProperty =
            BindableProperty.Create(nameof(Rating), typeof(int), typeof(RatingView), 0, propertyChanged: OnRatingChanged);

        public static readonly BindableProperty MaxRatingProperty =
            BindableProperty.Create(nameof(MaxRating), typeof(int), typeof(RatingView), 5);

        public int Rating
        {
            get => (int)GetValue(RatingProperty);
            set => SetValue(RatingProperty, value);
        }

        public int MaxRating
        {
            get => (int)GetValue(MaxRatingProperty);
            set => SetValue(MaxRatingProperty, value);
        }

        private readonly StackLayout _starsPanel = new StackLayout { Orientation = StackOrientation.Horizontal };

        public RatingView()
        {
            Content = _starsPanel;
            BuildStars();
        }

        private static void OnRatingChanged(BindableObject bindable, object oldValue, object newValue)
        {
            ((RatingView)bindable).UpdateStars();
        }

        private void BuildStars()
        {
            _starsPanel.Children.Clear();
            for (int i = 0; i < MaxRating; i++)
            {
                var tap = new TapGestureRecognizer();
                int index = i;
                tap.Tapped += (s, e) => Rating = index + 1;
                var star = new Image
                {
                    WidthRequest = 32,
                    HeightRequest = 32,
                    Aspect = Aspect.AspectFit,
                    GestureRecognizers = { tap }
                };
                _starsPanel.Children.Add(star);
            }
            UpdateStars();
        }

        private void UpdateStars()
        {
            for (int i = 0; i < _starsPanel.Children.Count; i++)
            {
                var img = (Image)_starsPanel.Children[i];
                img.Source = i < Rating ? "star_filled.png" : "star_outline.png";
            }
        }
    }
}

Step 2 – Add Resources

Include star_filled.png and star_outline.png in the Resources/Images folder and set their build action to MauiImage.

Step 3 – Use the Control

Reference the control in XAML and bind its Rating property.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:MauiApp.Controls;assembly=MauiApp"
             x:Class="MauiApp.Pages.RatePage">

    <StackLayout Padding="20">
        <Label Text="Rate this app:" FontSize="18" />
        <controls:RatingView Rating="{Binding UserRating}" />
        <Label Text="{Binding UserRating, StringFormat='Your rating: {0}'}" Margin="0,20,0,0" />
    </StackLayout>

</ContentPage>

Step 4 – ViewModel

Expose a property in your view model to hold the rating value.

public class RateViewModel : INotifyPropertyChanged
{
    private int _userRating;
    public int UserRating
    {
        get => _userRating;
        set
        {
            if (_userRating != value)
            {
                _userRating = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UserRating)));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Result

The control now displays interactive stars. Users can tap a star to set the rating, and the bound view‑model updates automatically.