.Net MAUI Entry control that provides a list of suggestions as the user types.
$ dotnet add package zoft.MauiExtensions.Controls.AutoCompleteEntryA powerful AutoCompleteEntry control for .NET MAUI that makes suggestions to users as they type. This control provides rich customization options, data templating support, and works consistently across all supported platforms.
Add the NuGet package to your project:
dotnet add package zoft.MauiExtensions.Controls.AutoCompleteEntry
Initialize the library in your MauiProgram.cs file:
using CommunityToolkit.Maui;
using zoft.MauiExtensions.Controls;
namespace AutoCompleteEntry.Sample
{
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiCommunityToolkit()
.UseZoftAutoCompleteEntry() // 👈 Add this line
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
return builder.Build();
}
}
}
Add this namespace to your XAML files:
xmlns:zoft="http://zoft.MauiExtensions/Controls"
| Property | Type | Default | Description |
|---|---|---|---|
ItemsSource | IList | null | Collection of suggestion items to display |
SelectedSuggestion | object | null | Currently selected suggestion item (two-way binding) |
DisplayMemberPath | string | "" | Property path for displaying items in the suggestion list |
TextMemberPath | string | "" | Property path for the text value when an item is selected |
ItemTemplate | DataTemplate | null | Custom template for rendering suggestion items |
IsSuggestionListOpen | bool | false | Controls whether the suggestion dropdown is open |
UpdateTextOnSelect | bool | true | Whether selecting an item updates the text field |
ShowBottomBorder | bool | true | Controls the visibility of the bottom border |
TextChangedCommand | ICommand | null | Command executed when text changes (receives text as parameter) |
AutoCompleteEntry inherits from Entry, so all standard Entry properties are available:
| Property | Description |
|---|---|
Text | The current text value |
Placeholder | Placeholder text when empty |
PlaceholderColor | Color of the placeholder text |
TextColor | Color of the input text |
FontSize, FontFamily, FontAttributes | Text formatting |
IsReadOnly | Whether the text can be edited |
MaxLength | Maximum character length |
CursorPosition | Current cursor position |
ClearButtonVisibility | When to show the clear button |
HorizontalTextAlignment, VerticalTextAlignment | Text alignment |
CharacterSpacing | Spacing between characters |
IsTextPredictionEnabled | Enable/disable text prediction |
ReturnType | Keyboard return key type |
| Event | EventArgs | Description |
|---|---|---|
TextChanged | AutoCompleteEntryTextChangedEventArgs | Fired when text changes (includes change reason) |
SuggestionChosen | AutoCompleteEntrySuggestionChosenEventArgs | Fired when a suggestion is selected |
CursorPositionChanged | AutoCompleteEntryCursorPositionChangedEventArgs | Fired when cursor position changes |
Plus all inherited Entry events: Completed, Focused, Unfocused
The filtering of results happens as the user types. You can respond to text changes using either:
🔗 Binding-Based Approach (Recommended)
TextChangedCommand for filtering logicSelectedSuggestion for the selected item⚡ Event-Based Approach
TextChanged event for filteringSuggestionChosen event for selection<zoft:AutoCompleteEntry
Placeholder="Search for a country or group"
ItemsSource="{Binding FilteredList}"
TextMemberPath="Country"
DisplayMemberPath="Country"
TextChangedCommand="{Binding TextChangedCommand}"
SelectedSuggestion="{Binding SelectedItem}"
HeightRequest="50" />
ViewModel Implementation:
public partial class SampleViewModel : ObservableObject
{
private readonly List<CountryItem> _allCountries = new()
{
new CountryItem { Group = "Group A", Country = "Ecuador" },
new CountryItem { Group = "Group B", Country = "Netherlands" },
// ... more items
};
[ObservableProperty]
private ObservableCollection<CountryItem> _filteredList;
[ObservableProperty]
private CountryItem _selectedItem;
[ObservableProperty]
private int _cursorPosition;
public SampleViewModel()
{
FilteredList = new(_allCountries);
}
[RelayCommand]
private void TextChanged(string text)
{
FilteredList.Clear();
var filtered = _allCountries.Where(item =>
item.Country.Contains(text, StringComparison.OrdinalIgnoreCase) ||
item.Group.Contains(text, StringComparison.OrdinalIgnoreCase));
foreach (var item in filtered)
FilteredList.Add(item);
}
}
public class CountryItem
{
public string Group { get; set; }
public string Country { get; set; }
}
<zoft:AutoCompleteEntry
Placeholder="Search countries with custom display"
ItemsSource="{Binding FilteredList}"
TextMemberPath="Country"
DisplayMemberPath="Country"
TextChangedCommand="{Binding TextChangedCommand}"
SelectedSuggestion="{Binding SelectedItem}"
ShowBottomBorder="{Binding ShowBottomBorder}"
HeightRequest="50">
<!-- 🎨 Custom item template for rich display -->
<zoft:AutoCompleteEntry.ItemTemplate>
<DataTemplate x:DataType="vm:CountryItem">
<Grid ColumnDefinitions="Auto,*,Auto"
Padding="12,8"
HeightRequest="60">
<!-- Flag or Group Indicator -->
<Border Grid.Column="0"
BackgroundColor="{Binding GroupColor}"
WidthRequest="4"
HeightRequest="40"
StrokeShape="RoundRectangle 2" />
<!-- Country Details -->
<StackLayout Grid.Column="1"
Margin="12,0">
<Label Text="{Binding Country}"
FontSize="16"
FontAttributes="Bold"
TextColor="Black" />
<Label Text="{Binding Group}"
FontSize="12"
TextColor="Gray" />
</StackLayout>
<!-- Population or other info -->
<Label Grid.Column="2"
Text="{Binding Population, StringFormat='{0:N0}'}"
FontSize="12"
TextColor="DarkGray"
VerticalOptions="Center" />
</Grid>
</DataTemplate>
</zoft:AutoCompleteEntry.ItemTemplate>
</zoft:AutoCompleteEntry>
<zoft:AutoCompleteEntry
Placeholder="Search for a country or group"
ItemsSource="{Binding FilteredList}"
TextMemberPath="Country"
DisplayMemberPath="Country"
TextChanged="AutoCompleteEntry_TextChanged"
SuggestionChosen="AutoCompleteEntry_SuggestionChosen"
CursorPositionChanged="AutoCompleteEntry_CursorPositionChanged"
ClearButtonVisibility="WhileEditing"
HeightRequest="50" />
Code-Behind Implementation:
private void AutoCompleteEntry_TextChanged(object sender, AutoCompleteEntryTextChangedEventArgs e)
{
// Only filter when the user is actually typing
if (e.Reason == AutoCompleteEntryTextChangeReason.UserInput)
{
var autoComplete = sender as AutoCompleteEntry;
ViewModel.FilterList(autoComplete.Text);
}
}
private void AutoCompleteEntry_SuggestionChosen(object sender, AutoCompleteEntrySuggestionChosenEventArgs e)
{
// Handle the selected suggestion
if (e.SelectedItem is CountryItem selectedCountry)
{
ViewModel.SelectedItem = selectedCountry;
// Perform additional actions like navigation or validation
}
}
private void AutoCompleteEntry_CursorPositionChanged(object sender, AutoCompleteEntryCursorPositionChangedEventArgs e)
{
// Track cursor position for analytics or custom behavior
Console.WriteLine($"Cursor moved to position: {e.NewCursorPosition}");
}
// Programmatically open/close the suggestion list
autoCompleteEntry.IsSuggestionListOpen = true;
// Control text updates on selection
autoCompleteEntry.UpdateTextOnSelect = false; // Keep original text when selecting
// Customize appearance
autoCompleteEntry.ShowBottomBorder = false; // Remove bottom border
autoCompleteEntry.ClearButtonVisibility = ClearButtonVisibility.WhileEditing;
// Handle selection programmatically
autoCompleteEntry.SelectedSuggestion = mySelectedItem;
| Feature | Windows | Android | iOS | MacCatalyst | Notes |
|---|---|---|---|---|---|
| Core Functionality | |||||
| Text Input & Filtering | ✅ | ✅ | ✅ | ✅ | Full support |
| ItemsSource Binding | ✅ | ✅ | ✅ | ✅ | Full support |
| Selection Events | ✅ | ✅ | ✅ | ✅ | Full support |
| Appearance & Styling | |||||
| ItemTemplate | ❌ | ✅ | ✅ | ✅ | Windows: Planned for future release |
| ShowBottomBorder | ❌ | ✅ | ✅ | ✅ | Windows: Planned for future release |
| Text Styling | ✅ | ✅ | ✅ | ✅ | Fonts, colors, alignment |
| Behavior | |||||
| UpdateTextOnSelect | ✅ | ✅ | ✅ | ✅ | Full support |
| IsSuggestionListOpen | ✅ | ✅ | ✅ | ✅ | Full support |
| CursorPosition | ✅ | ✅ | ✅ | ✅ | Full support |
| Entry Features | |||||
| ClearButtonVisibility | ✅ | ✅ | ✅ | ✅ | Full support |
| Placeholder | ✅ | ✅ | ✅ | ✅ | Full support |
| IsReadOnly | ✅ | ✅ | ✅ | ✅ | Full support |
| MaxLength | ✅ | ✅ | ✅ | ✅ | Full support |
While the AutoCompleteEntry works great on Windows, some advanced styling features are still in development:
All core functionality including filtering, selection, and data binding works perfectly on Windows.
Create rich, interactive suggestion lists with custom DataTemplates:
<zoft:AutoCompleteEntry.ItemTemplate>
<DataTemplate x:DataType="models:Product">
<Grid ColumnDefinitions="60,*,Auto"
RowDefinitions="Auto,Auto"
Padding="16,12">
<!-- Product Image -->
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
WidthRequest="50"
HeightRequest="50"
Aspect="AspectFill" />
<!-- Product Info -->
<Label Grid.Column="1"
Text="{Binding Name}"
FontSize="16"
FontAttributes="Bold" />
<Label Grid.Column="1" Grid.Row="1"
Text="{Binding Category}"
FontSize="12"
TextColor="Gray" />
<!-- Price -->
<Label Grid.Column="2" Grid.RowSpan="2"
Text="{Binding Price, StringFormat='${0:F2}'}"
FontSize="14"
FontAttributes="Bold"
VerticalOptions="Center"
HorizontalOptions="End" />
</Grid>
</DataTemplate>
</zoft:AutoCompleteEntry.ItemTemplate>
<Style x:Key="CustomAutoCompleteStyle" TargetType="zoft:AutoCompleteEntry">
<Setter Property="BackgroundColor" Value="{DynamicResource SurfaceColor}" />
<Setter Property="TextColor" Value="{DynamicResource OnSurfaceColor}" />
<Setter Property="PlaceholderColor" Value="{DynamicResource OnSurfaceVariantColor}" />
<Setter Property="FontSize" Value="16" />
<Setter Property="HeightRequest" Value="56" />
<Setter Property="Margin" Value="16,8" />
<Setter Property="ShowBottomBorder" Value="True" />
</Style>
// Example: Debounced filtering
private CancellationTokenSource _filterCancellation;
[RelayCommand]
private async Task TextChanged(string text)
{
_filterCancellation?.Cancel();
_filterCancellation = new CancellationTokenSource();
try
{
await Task.Delay(300, _filterCancellation.Token); // Debounce
await FilterItemsAsync(text);
}
catch (TaskCanceledException)
{
// Filtering was cancelled by newer input
}
}
Issue: Suggestions not appearing
ItemsSource is properly bound and contains dataDisplayMemberPath matches your data model propertiesIssue: Selection not working
SelectedSuggestion binding is two-wayTextMemberPath property is correctly setItemsSourceIssue: Custom templates not rendering (Windows)
Issue: Performance issues with large datasets
If you find this project helpful, please consider supporting its development:
Your support helps maintain and improve this project for the entire .NET MAUI community. Thank you! 🙏
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the MIT License - see the LICENSE.md file for details.