A high-performance, AI-powered AutoComplete (TypeAhead) component for Blazor applications with semantic search, OData integration, and vector database support. Also known as: autosuggest, combobox, searchable dropdown, live search.
Live Demo | NuGet Package | Get Started
- AI-Powered Semantic Search - Find results by meaning, not just keywords, using OpenAI, Azure OpenAI, or any embedding provider
- OData v3/v4 Integration - Server-side filtering with automatic
$filterquery generation - 5 Vector Database Providers - PostgreSQL/pgvector, Azure AI Search, Pinecone, Qdrant, CosmosDB
- < 15KB Gzipped - Lightweight core with zero runtime reflection
- Native AOT Ready - Source generators enable full ahead-of-time compilation and trimming
- WCAG 2.1 AA Accessible - ARIA 1.2 combobox pattern with full keyboard navigation
- 100K+ Items - Virtualization maintains 60fps scrolling with massive datasets
- 8 Built-in Display Modes - Eliminate custom template boilerplate
- Installation
- Quick Start
- Core Features
- Theming System
- OData Integration
- AI Semantic Search
- Vector Database Providers
- Accessibility
- Security
- API Reference
- Packages
- Requirements
- Performance
- Troubleshooting
- Support
dotnet add package EasyAppDev.Blazor.AutoComplete# OData server-side filtering
dotnet add package EasyAppDev.Blazor.AutoComplete.OData
# AI semantic search with embeddings
dotnet add package EasyAppDev.Blazor.AutoComplete.AI
# Vector database providers
dotnet add package EasyAppDev.Blazor.AutoComplete.AI.PostgreSql
dotnet add package EasyAppDev.Blazor.AutoComplete.AI.AzureSearch
dotnet add package EasyAppDev.Blazor.AutoComplete.AI.Pinecone
dotnet add package EasyAppDev.Blazor.AutoComplete.AI.Qdrant
dotnet add package EasyAppDev.Blazor.AutoComplete.AI.CosmosDbAdd to Program.cs:
using EasyAppDev.Blazor.AutoComplete.Extensions;
builder.Services.AddAutoComplete();This registers:
IThemeManager- Theme CSS generation (singleton)IAutoCompleteServiceFactory- Component service factory (singleton)
Add to App.razor or index.html:
<head>
<link href="_content/EasyAppDev.Blazor.AutoComplete/styles/autocomplete.base.css" rel="stylesheet" />
<script src="_content/EasyAppDev.Blazor.AutoComplete/scripts/theme-loader.js"></script>
</head>@using EasyAppDev.Blazor.AutoComplete
<AutoComplete TItem="Product"
Items="@products"
TextField="@(p => p.Name)"
@bind-Value="@selectedProduct"
Placeholder="Search products..." />
@code {
private List<Product> products = new()
{
new Product { Id = 1, Name = "Apple", Category = "Fruits" },
new Product { Id = 2, Name = "Banana", Category = "Fruits" },
new Product { Id = 3, Name = "Carrot", Category = "Vegetables" }
};
private Product? selectedProduct;
}Note: The component works without
AddAutoComplete()using fallback instances, but registering services enables proper dependency injection, testability, and singleton behavior.
Search across multiple properties simultaneously:
<AutoComplete TItem="Product"
Items="@products"
SearchFields="@(p => new[] { p.Name, p.Description, p.Category, p.SKU })"
TextField="@(p => p.Name)"
@bind-Value="@selectedProduct" />When SearchFields is provided, filtering matches against all specified fields with OR logic.
Four built-in filtering algorithms:
<!-- Prefix matching (default, fastest) -->
<AutoComplete FilterStrategy="FilterStrategy.StartsWith" ... />
<!-- Substring matching anywhere -->
<AutoComplete FilterStrategy="FilterStrategy.Contains" ... />
<!-- Typo-tolerant with Levenshtein distance -->
<AutoComplete FilterStrategy="FilterStrategy.Fuzzy" ... />
<!-- Custom filter implementation -->
<AutoComplete FilterStrategy="FilterStrategy.Custom"
CustomFilter="@myFilterEngine" ... />Fuzzy Search uses Levenshtein distance algorithm with configurable tolerance for handling typos and misspellings.
Eight built-in layouts eliminate custom template boilerplate:
@using EasyAppDev.Blazor.AutoComplete.Options
<!-- Simple text only -->
<AutoComplete DisplayMode="ItemDisplayMode.Simple" ... />
<!-- Title + description (two-line) -->
<AutoComplete DisplayMode="ItemDisplayMode.TitleWithDescription"
DescriptionField="@(p => p.Category)" ... />
<!-- Title + right-aligned badge -->
<AutoComplete DisplayMode="ItemDisplayMode.TitleWithBadge"
BadgeField="@(p => $"${p.Price:F2}")"
BadgeClass="badge bg-success" ... />
<!-- Title + description + badge -->
<AutoComplete DisplayMode="ItemDisplayMode.TitleDescriptionBadge"
DescriptionField="@(p => p.Category)"
BadgeField="@(p => p.Stock.ToString())" ... />
<!-- Icon/emoji + title -->
<AutoComplete DisplayMode="ItemDisplayMode.IconWithTitle"
IconField="@(p => p.Emoji)" ... />
<!-- Icon + title + description -->
<AutoComplete DisplayMode="ItemDisplayMode.IconTitleDescription"
IconField="@(p => p.Emoji)"
DescriptionField="@(p => p.Category)" ... />
<!-- Card layout with all fields -->
<AutoComplete DisplayMode="ItemDisplayMode.Card"
IconField="@(p => p.Emoji)"
SubtitleField="@(p => p.Category)"
DescriptionField="@(p => p.Description)"
BadgeField="@(p => $"${p.Price:F2}")" ... />Available modes: Custom, Simple, TitleWithDescription, TitleWithBadge, TitleDescriptionBadge, IconWithTitle, IconTitleDescription, Card
Group items by category with custom headers:
<AutoComplete TItem="Product"
Items="@products"
GroupBy="@(p => p.Category)"
TextField="@(p => p.Name)"
@bind-Value="@selectedProduct">
<GroupTemplate Context="group">
<div class="group-header">
<strong>@group.Key</strong>
<span class="badge bg-secondary">@group.Count()</span>
</div>
</GroupTemplate>
</AutoComplete>Handle massive datasets with smooth 60fps scrolling:
<AutoComplete TItem="Product"
Items="@largeDataset"
Virtualize="true"
VirtualizationThreshold="100"
ItemHeight="40"
TextField="@(p => p.Name)"
@bind-Value="@selectedProduct" />Virtualization automatically activates when item count exceeds VirtualizationThreshold. Tested with 100,000+ items.
Fetch data from remote APIs:
<AutoComplete TItem="Product"
DataSource="@dataSource"
TextField="@(p => p.Name)"
@bind-Value="@selectedProduct" />
@code {
private IAutoCompleteDataSource<Product> dataSource = new RemoteDataSource<Product>(
async (query, cancellationToken) =>
await httpClient.GetFromJsonAsync<List<Product>>(
$"/api/products?search={Uri.EscapeDataString(query)}",
cancellationToken)
);
}Full control over rendering:
<AutoComplete TItem="Product"
Items="@products"
TextField="@(p => p.Name)"
@bind-Value="@selectedProduct">
<ItemTemplate Context="product">
<div class="d-flex align-items-center gap-2">
<img src="@product.ImageUrl" width="32" height="32" alt="@product.Name" />
<div>
<strong>@product.Name</strong>
<small class="text-muted d-block">@product.Category</small>
</div>
<span class="ms-auto badge bg-primary">$@product.Price</span>
</div>
</ItemTemplate>
<NoResultsTemplate>
<div class="text-center py-3">
<i class="bi bi-search"></i>
<p>No products found</p>
</div>
</NoResultsTemplate>
<LoadingTemplate>
<div class="text-center py-3">
<div class="spinner-border spinner-border-sm"></div>
<span>Searching...</span>
</div>
</LoadingTemplate>
<HeaderTemplate>
<div class="dropdown-header">Select a product</div>
</HeaderTemplate>
<FooterTemplate>
<div class="dropdown-footer text-muted small">
Type to search products
</div>
</FooterTemplate>
</AutoComplete>Build complex configurations with method chaining:
var config = AutoCompleteConfig<Product>.Create()
.WithItems(products)
.WithTextField(p => p.Name)
.WithSearchFields(p => new[] { p.Name, p.Description, p.Category })
.WithDisplayMode(ItemDisplayMode.TitleWithDescription)
.WithTitleAndDescription(p => p.Description)
.WithFilterStrategy(FilterStrategy.Contains)
.WithTheme(Theme.Auto)
.WithBootstrapTheme(BootstrapTheme.Primary)
.WithVirtualization(threshold: 1000, itemHeight: 45)
.WithGrouping(p => p.Category)
.WithDebounce(300)
.Build();<AutoComplete TItem="Product" Config="@config" @bind-Value="@selectedProduct" />Four professional design system presets:
<!-- Google Material Design 3 -->
<AutoComplete ThemePreset="ThemePreset.Material" ... />
<!-- Microsoft Fluent Design -->
<AutoComplete ThemePreset="ThemePreset.Fluent" ... />
<!-- Clean, minimalist design -->
<AutoComplete ThemePreset="ThemePreset.Modern" ... />
<!-- Bootstrap 5 integration -->
<AutoComplete ThemePreset="ThemePreset.Bootstrap" ... />Automatic system preference detection:
<!-- Follow system preference (default) -->
<AutoComplete Theme="Theme.Auto" ... />
<!-- Force light mode -->
<AutoComplete Theme="Theme.Light" ... />
<!-- Force dark mode -->
<AutoComplete Theme="Theme.Dark" ... />Nine Bootstrap 5 color schemes:
<AutoComplete BootstrapTheme="BootstrapTheme.Primary" ... />
<AutoComplete BootstrapTheme="BootstrapTheme.Secondary" ... />
<AutoComplete BootstrapTheme="BootstrapTheme.Success" ... />
<AutoComplete BootstrapTheme="BootstrapTheme.Danger" ... />
<AutoComplete BootstrapTheme="BootstrapTheme.Warning" ... />
<AutoComplete BootstrapTheme="BootstrapTheme.Info" ... />
<AutoComplete BootstrapTheme="BootstrapTheme.Light" ... />
<AutoComplete BootstrapTheme="BootstrapTheme.Dark" ... />Three size variants:
<AutoComplete Size="ComponentSize.Compact" ... /> <!-- Smaller padding -->
<AutoComplete Size="ComponentSize.Default" ... /> <!-- Standard size -->
<AutoComplete Size="ComponentSize.Large" ... /> <!-- Larger padding -->Simple overrides (1-5 properties):
<AutoComplete PrimaryColor="#FF6B6B"
BackgroundColor="#FFFFFF"
TextColor="#212529"
BorderColor="#ced4da"
BorderRadius="8px"
FontFamily="Inter, sans-serif"
FontSize="14px"
DropdownShadow="0 4px 12px rgba(0,0,0,0.15)"
... />Structured overrides (advanced):
<AutoComplete ThemeOverrides="@(new ThemeOptions {
Colors = new ColorOptions {
Primary = "#FF6B6B",
Hover = "#f8f9fa",
Selected = "#e9ecef"
},
Spacing = new SpacingOptions {
BorderRadius = "8px",
InputPadding = "12px 16px"
},
Typography = new TypographyOptions {
FontFamily = "Inter, sans-serif",
FontSize = "14px"
},
Effects = new EffectOptions {
DropdownShadow = "0 4px 12px rgba(0,0,0,0.15)",
TransitionDuration = "150ms"
}
})" ... />Precedence: Individual parameters > ThemeOverrides > Theme presets > Defaults
The OData package enables server-side filtering with automatic $filter query generation for OData v3 and v4 endpoints.
dotnet add package EasyAppDev.Blazor.AutoComplete.OData@using EasyAppDev.Blazor.AutoComplete
@using EasyAppDev.Blazor.AutoComplete.OData
@inject HttpClient Http
<AutoComplete TItem="Product"
DataSource="@_odataSource"
TextField="@(p => p.Name)"
@bind-Value="@selectedProduct"
Placeholder="Search products..." />
@code {
private ODataDataSource<Product> _odataSource = null!;
private Product? selectedProduct;
protected override void OnInitialized()
{
var options = new ODataOptions
{
EndpointUrl = "https://api.example.com/odata/Products",
FilterStrategy = ODataFilterStrategy.StartsWith,
Top = 20,
CaseInsensitive = true
};
_odataSource = new ODataDataSource<Product>(Http, options, "Name");
}
}Generated OData query:
GET /odata/Products?$filter=startswith(tolower(Name),'apple')&$top=20
Search across multiple fields with OR combination:
// Search Name, Description, and Category simultaneously
_odataSource = new ODataDataSource<Product>(
Http,
options,
searchFieldNames: new[] { "Name", "Description", "Category" }
);Generated OData query:
$filter=(startswith(tolower(Name),'search') or startswith(tolower(Description),'search') or startswith(tolower(Category),'search'))
For legacy OData v3 endpoints:
var options = new ODataOptions
{
EndpointUrl = "https://legacy-api.example.com/odata/Products",
Version = ODataVersion.V3, // Use v3 syntax
FilterStrategy = ODataFilterStrategy.Contains
};| Strategy | OData v4 | OData v3 |
|---|---|---|
StartsWith |
startswith(field,'value') |
startswith(field,'value') |
Contains |
contains(field,'value') |
substringof('value',field) |
FuzzyFallback |
contains() + client re-rank |
substringof() + client re-rank |
FuzzyFallback performs server-side contains filtering, then applies Levenshtein distance re-ranking on the client for typo tolerance.
| Parameter | Type | Default | Description |
|---|---|---|---|
EndpointUrl |
string |
Required | OData endpoint URL |
Version |
ODataVersion |
V4 |
OData protocol version (V3 or V4) |
FilterStrategy |
ODataFilterStrategy |
StartsWith |
Server-side filter type |
Top |
int |
100 |
Maximum results ($top) |
Select |
string[]? |
null |
Fields to return ($select) |
OrderBy |
string? |
null |
Sort order ($orderby) |
AdditionalFilter |
string? |
null |
Static filter ANDed with search |
CaseInsensitive |
bool |
true |
Wrap fields with tolower() |
MinSearchLength |
int |
1 |
Minimum characters before API call |
TimeoutSeconds |
int |
30 |
HTTP request timeout |
CustomHeaders |
Dictionary<string,string>? |
null |
HTTP headers (Authorization, etc.) |
ResultsPropertyName |
string |
"value" |
JSON property containing results array |
IncludeCount |
bool |
false |
Include $count in response |
var config = AutoCompleteConfig<Product>.Create()
.WithODataSource(Http, "https://api.example.com/odata/Products", "Name",
opts => {
opts.FilterStrategy = ODataFilterStrategy.Contains;
opts.Top = 25;
opts.OrderBy = "Name asc";
opts.AdditionalFilter = "IsActive eq true";
})
.WithDisplayMode(ItemDisplayMode.TitleWithDescription)
.WithTitleAndDescription(p => p.Description)
.Build();Configuration-based:
// appsettings.json
{
"ODataSettings": {
"EndpointUrl": "https://api.example.com/odata/Products",
"FilterStrategy": "Contains",
"Top": 50
}
}builder.Services.AddAutoCompleteOData(builder.Configuration, "ODataSettings");Explicit configuration:
builder.Services.AddAutoCompleteOData(
"https://api.example.com/odata/Products",
options => {
options.FilterStrategy = ODataFilterStrategy.Contains;
options.Top = 50;
options.CustomHeaders = new Dictionary<string, string>
{
["Authorization"] = "Bearer your-token"
};
});The AI package enables meaning-based search using text embeddings and vector similarity. Find "automobile" when searching for "car".
dotnet add package EasyAppDev.Blazor.AutoComplete.AIOpenAI Configuration:
// Using appsettings.json
builder.Services.AddAutoCompleteSemanticSearch(builder.Configuration);
// Or explicit configuration
builder.Services.AddAutoCompleteSemanticSearch(
apiKey: "sk-...",
model: "text-embedding-3-small" // Default model
);// appsettings.json
{
"OpenAI": {
"ApiKey": "sk-...",
"Model": "text-embedding-3-small"
}
}Azure OpenAI Configuration:
builder.Services.AddAutoCompleteSemanticSearchWithAzure(
endpoint: "https://my-resource.openai.azure.com/",
apiKey: "your-azure-openai-key",
deploymentName: "text-embedding-ada-002"
);// appsettings.json
{
"AzureOpenAI": {
"Endpoint": "https://my-resource.openai.azure.com/",
"ApiKey": "your-azure-openai-key",
"DeploymentName": "text-embedding-ada-002"
}
}High-level wrapper for AI-powered search:
@using EasyAppDev.Blazor.AutoComplete.AI
<SemanticAutoComplete TItem="Document"
Items="@documents"
TextField="@(d => d.Title)"
SearchFields="@(d => new[] { d.Title, d.Description, d.Tags })"
SimilarityThreshold="0.15"
MinSearchLength="3"
DebounceMs="500"
PreWarmCache="true"
ShowCacheStatus="true"
@bind-Value="@selectedDocument"
Placeholder="Search by meaning..." />
@code {
private List<Document> documents = new();
private Document? selectedDocument;
}The AI package includes an intelligent dual-caching system to minimize API costs:
Item Cache:
- Caches embeddings for your data items
- Default TTL: 1 hour
- Default max size: 10,000 items
- LRU eviction when full
Query Cache:
- Caches embeddings for user search queries
- Default TTL: 15 minutes
- Default max size: 1,000 queries
- LRU eviction when full
Pre-warming:
<SemanticAutoComplete PreWarmCache="true" ... />Generates embeddings for all items on initialization. Shows progress via CacheWarmingProgress event.
| Parameter | Type | Default | Description |
|---|---|---|---|
SimilarityThreshold |
float |
0.15 |
Minimum cosine similarity (0-1) for results |
MinSearchLength |
int |
3 |
Characters before semantic search triggers |
DebounceMs |
int |
500 |
Delay before API call |
MaxResults |
int? |
null |
Maximum results (null = all matching) |
ItemCacheDuration |
TimeSpan |
1 hour |
Item embedding cache TTL |
QueryCacheDuration |
TimeSpan |
15 min |
Query embedding cache TTL |
MaxItemCacheSize |
int |
10,000 |
Maximum cached item embeddings |
MaxQueryCacheSize |
int |
1,000 |
Maximum cached query embeddings |
PreWarmCache |
bool |
false |
Generate all embeddings on init |
ShowCacheStatus |
bool |
true |
Display cache hit rate statistics |
For production deployments with persistent storage and scalable semantic search, use external vector database providers. These eliminate embedding regeneration on restart and support millions of items.
| Provider | Package | Best For |
|---|---|---|
| PostgreSQL/pgvector | EasyAppDev.Blazor.AutoComplete.AI.PostgreSql |
Self-hosted, existing Postgres infrastructure |
| Azure AI Search | EasyAppDev.Blazor.AutoComplete.AI.AzureSearch |
Enterprise, hybrid search, managed service |
| Pinecone | EasyAppDev.Blazor.AutoComplete.AI.Pinecone |
Serverless, automatic scaling |
| Qdrant | EasyAppDev.Blazor.AutoComplete.AI.Qdrant |
Open-source, self-hosted, advanced filtering |
| Azure CosmosDB | EasyAppDev.Blazor.AutoComplete.AI.CosmosDb |
Global distribution, multi-model |
dotnet add package EasyAppDev.Blazor.AutoComplete.AI.PostgreSqlConfiguration:
{
"VectorSearch": {
"PostgreSQL": {
"ConnectionString": "Host=localhost;Database=myapp;Username=user;Password=pass",
"CollectionName": "products",
"EmbeddingDimensions": 1536
}
},
"OpenAI": {
"ApiKey": "sk-..."
}
}using EasyAppDev.Blazor.AutoComplete.AI.PostgreSql.Extensions;
builder.Services.AddAutoCompletePostgres<Product>(
builder.Configuration,
textSelector: p => $"{p.Name} {p.Description} {p.Category}",
idSelector: p => p.Id.ToString());
builder.Services.AddAutoCompleteVectorSearch<Product>(builder.Configuration);Features:
- 6 distance functions (Cosine, L2, DotProduct, Manhattan, Hamming, Jaccard)
- HNSW indexing for fast approximate nearest neighbor search
- Self-hosted with your existing PostgreSQL infrastructure
dotnet add package EasyAppDev.Blazor.AutoComplete.AI.AzureSearchConfiguration:
{
"VectorSearch": {
"AzureSearch": {
"Endpoint": "https://my-search.search.windows.net",
"ApiKey": "your-search-api-key",
"IndexName": "products",
"EmbeddingDimensions": 1536,
"EnableHybridSearch": true
}
},
"AzureOpenAI": {
"Endpoint": "https://my-openai.openai.azure.com/",
"ApiKey": "your-openai-key",
"DeploymentName": "text-embedding-ada-002"
}
}using EasyAppDev.Blazor.AutoComplete.AI.AzureSearch.Extensions;
builder.Services.AddAutoCompleteAzureSearch<Product>(
builder.Configuration,
textSelector: p => $"{p.Name} {p.Description}",
idSelector: p => p.Id.ToString());
builder.Services.AddAutoCompleteVectorSearchWithAzure<Product>(builder.Configuration);Features:
- Hybrid search (vector + keyword)
- Semantic ranking
- Enterprise-grade managed service
dotnet add package EasyAppDev.Blazor.AutoComplete.AI.Pinecone{
"VectorSearch": {
"Pinecone": {
"ApiKey": "your-pinecone-api-key",
"IndexName": "products",
"Namespace": "default",
"EmbeddingDimensions": 1536
}
}
}builder.Services.AddAutoCompletePinecone<Product>(
builder.Configuration,
textSelector: p => p.Name);Features:
- Serverless architecture
- Namespace isolation
- Automatic scaling
- High availability
dotnet add package EasyAppDev.Blazor.AutoComplete.AI.Qdrant{
"VectorSearch": {
"Qdrant": {
"Host": "localhost",
"Port": 6334,
"Https": false,
"ApiKey": "your-api-key-for-cloud",
"CollectionName": "products",
"EmbeddingDimensions": 1536
}
}
}builder.Services.AddAutoCompleteQdrant<Product>(
builder.Configuration,
textSelector: p => p.Name);Features:
- Open-source
- Self-hosted or cloud
- Advanced filtering with payload
- HNSW indexing
dotnet add package EasyAppDev.Blazor.AutoComplete.AI.CosmosDb{
"VectorSearch": {
"CosmosDb": {
"ConnectionString": "AccountEndpoint=https://myaccount.documents.azure.com:443/;AccountKey=...",
"DatabaseName": "myapp",
"ContainerName": "products",
"EmbeddingDimensions": 1536,
"VectorIndexType": "quantizedFlat"
}
}
}builder.Services.AddAutoCompleteCosmosDb<Product>(
builder.Configuration,
textSelector: p => p.Name);Features:
- Multi-model database
- Global distribution
- Integrated NoSQL
- Multi-region replication
Before searching, index your data:
public class ProductService
{
private readonly IVectorIndexer<Product> _indexer;
public ProductService(IVectorIndexer<Product> indexer)
{
_indexer = indexer;
}
public async Task IndexProductsAsync(IEnumerable<Product> products)
{
// Ensure collection/index exists
await _indexer.EnsureCollectionExistsAsync();
// Index all items (with progress reporting)
_indexer.ProgressChanged += (sender, e) =>
Console.WriteLine($"Indexed {e.ProcessedItems}/{e.TotalItems}");
await _indexer.IndexAsync(products);
}
public async Task IndexSingleProductAsync(Product product)
{
// Upsert single item
await _indexer.IndexAsync(product);
}
}| Scenario | Recommendation |
|---|---|
| Development/Prototyping | In-memory SemanticSearchDataSource |
| Small datasets (< 10K items) | Either approach works |
| Production (> 10K items) | Use vector provider |
| Persistence across restarts | Use vector provider |
| Multi-instance deployment | Use vector provider (shared database) |
| Hybrid search (vector + keyword) | Azure AI Search or CosmosDB |
The component follows WCAG 2.1 AA guidelines and implements the ARIA 1.2 Combobox pattern.
| Key | Action |
|---|---|
Arrow Down |
Open dropdown / Select next item |
Arrow Up |
Select previous item |
Enter |
Confirm selection |
Escape |
Close dropdown |
Home |
Jump to first item |
End |
Jump to last item |
Tab |
Close dropdown and move focus |
role="combobox"on inputrole="listbox"on dropdownrole="option"on itemsaria-expanded- Dropdown statearia-activedescendant- Current focusaria-selected- Selected item
<label for="product-search">Search Products:</label>
<AutoComplete InputId="product-search"
AriaLabel="Search and select a product"
... />- High Contrast Mode - Respects
prefers-contrast: high - Reduced Motion - Respects
prefers-reduced-motion: reduce - RTL Support - Set
RightToLeft="true"for right-to-left languages - Form Integration - Works with Blazor
EditFormvalidation
- CSS Sanitization - Theme values validated against allowlists
- Input Limits -
MaxSearchLengthprevents memory exhaustion (default 500, max 2000) - ReDoS Protection - Regex patterns use 100ms timeouts
- OData Escaping - Search terms properly escaped to prevent injection
- API Key Redaction - Sensitive data removed from error messages and logs
- Rate Limiting - Token bucket algorithm protects embedding API quotas
- Memory Pressure Monitoring - Automatic cache eviction under memory pressure
// All security limits defined in AutoCompleteConstants
public static class AutoCompleteConstants
{
public const int DefaultMaxSearchLength = 500;
public const int AbsoluteMaxSearchLength = 2000;
public const int RegexTimeoutMs = 100;
// ... more constants
}| Parameter | Type | Default | Description |
|---|---|---|---|
Items |
IEnumerable<TItem>? |
null |
Collection of items |
DataSource |
IAutoCompleteDataSource<TItem>? |
null |
Async data source (precedence over Items) |
Value |
TItem? |
null |
Selected value (two-way bindable) |
ValueChanged |
EventCallback<TItem?> |
Selection change event | |
TextField |
Expression<Func<TItem, string>>? |
null |
Display text property selector |
SearchFields |
Expression<Func<TItem, string[]>>? |
null |
Multi-field search selectors |
Placeholder |
string? |
null |
Input placeholder text |
MinSearchLength |
int |
1 |
Minimum characters before search |
MaxSearchLength |
int |
500 |
Maximum input length |
MaxDisplayedItems |
int |
100 |
Maximum items shown |
DebounceMs |
int |
300 |
Debounce delay in milliseconds |
AllowClear |
bool |
true |
Show clear button |
Disabled |
bool |
false |
Disable the component |
CloseOnSelect |
bool |
true |
Close dropdown on selection |
Config |
AutoCompleteConfig<TItem>? |
null |
Fluent configuration object |
| Parameter | Type | Default | Description |
|---|---|---|---|
FilterStrategy |
FilterStrategy |
StartsWith |
Filter algorithm |
CustomFilter |
IFilterEngine<TItem>? |
null |
Custom filter implementation |
| Parameter | Type | Default | Description |
|---|---|---|---|
DisplayMode |
ItemDisplayMode |
Custom |
Built-in display layout |
DescriptionField |
Expression<Func<TItem, string>>? |
null |
Description property |
BadgeField |
Expression<Func<TItem, string>>? |
null |
Badge property |
IconField |
Expression<Func<TItem, string>>? |
null |
Icon/emoji property |
SubtitleField |
Expression<Func<TItem, string>>? |
null |
Subtitle (Card mode) |
BadgeClass |
string |
"badge bg-primary" |
Badge CSS class |
| Parameter | Type | Default | Description |
|---|---|---|---|
Theme |
Theme |
Auto |
Light/Dark/Auto |
ThemePreset |
ThemePreset |
None |
Design system preset |
BootstrapTheme |
BootstrapTheme |
Default |
Bootstrap color variant |
Size |
ComponentSize |
Default |
Component size |
ThemeOverrides |
ThemeOptions? |
null |
Structured theme overrides |
PrimaryColor |
string? |
null |
Primary color override |
BackgroundColor |
string? |
null |
Background color override |
TextColor |
string? |
null |
Text color override |
BorderColor |
string? |
null |
Border color override |
BorderRadius |
string? |
null |
Border radius override |
FontFamily |
string? |
null |
Font family override |
FontSize |
string? |
null |
Font size override |
DropdownShadow |
string? |
null |
Dropdown shadow override |
EnableThemeTransitions |
bool |
true |
Enable smooth transitions |
RightToLeft |
bool |
false |
RTL text direction |
| Parameter | Type | Default | Description |
|---|---|---|---|
Virtualize |
bool |
false |
Enable virtualization |
VirtualizationThreshold |
int |
100 |
Item count threshold |
ItemHeight |
float |
40 |
Item height in pixels |
| Parameter | Type | Default | Description |
|---|---|---|---|
GroupBy |
Expression<Func<TItem, object>>? |
null |
Grouping property selector |
GroupTemplate |
RenderFragment<IGrouping<object, TItem>>? |
null |
Group header template |
| Parameter | Type | Description |
|---|---|---|
ItemTemplate |
RenderFragment<TItem>? |
Custom item rendering |
NoResultsTemplate |
RenderFragment? |
No results message |
LoadingTemplate |
RenderFragment? |
Loading indicator |
HeaderTemplate |
RenderFragment? |
Dropdown header |
FooterTemplate |
RenderFragment? |
Dropdown footer |
| Parameter | Type | Description |
|---|---|---|
AriaLabel |
string? |
ARIA label for screen readers |
InputId |
string? |
Input element ID for label association |
ValueExpression |
Expression<Func<TItem?>>? |
Form validation expression |
| Package | Description | Size |
|---|---|---|
EasyAppDev.Blazor.AutoComplete |
Core component library | < 15KB gzipped |
EasyAppDev.Blazor.AutoComplete.Generators |
Source generators (build-time only) | 0KB runtime |
EasyAppDev.Blazor.AutoComplete.OData |
OData v3/v4 server-side filtering | < 5KB gzipped |
EasyAppDev.Blazor.AutoComplete.AI |
Semantic search with embeddings | < 25KB gzipped |
EasyAppDev.Blazor.AutoComplete.AI.PostgreSql |
PostgreSQL/pgvector provider | < 10KB gzipped |
EasyAppDev.Blazor.AutoComplete.AI.AzureSearch |
Azure AI Search provider | < 10KB gzipped |
EasyAppDev.Blazor.AutoComplete.AI.Pinecone |
Pinecone provider | < 10KB gzipped |
EasyAppDev.Blazor.AutoComplete.AI.Qdrant |
Qdrant provider | < 10KB gzipped |
EasyAppDev.Blazor.AutoComplete.AI.CosmosDb |
Azure CosmosDB provider | < 10KB gzipped |
- .NET 8.0, .NET 9.0, or .NET 10.0
- Blazor WebAssembly, Blazor Server, or Blazor Auto/United render modes
- Modern browser with ES6 support
- Core bundle size: < 15KB gzipped
- Filter 100K items: < 100ms
- Virtualization: 60fps scrolling with 100K+ items
- First render: < 50ms
- SIMD acceleration: 3-5x speedup for semantic similarity calculations
Component not rendering / styles missing
<!-- Ensure CSS is added to App.razor or index.html -->
<link href="_content/EasyAppDev.Blazor.AutoComplete/styles/autocomplete.base.css" rel="stylesheet" />Dropdown not opening
- Check that
ItemsorDataSourceis not null/empty - Verify
MinSearchLength- default is 1 character before filtering
Items not filtering
- Ensure
TextFieldexpression returns a non-null string - Check
FilterStrategymatches your use case (StartsWith vs Contains)
Virtualization not working
- Set
Virtualize="true"explicitly - Ensure
ItemHeightmatches your actual item height in pixels - Check that item count exceeds
VirtualizationThreshold(default: 100)
OData requests failing
- Verify
EndpointUrlis correct and accessible - Check CORS configuration on server
- Use browser DevTools Network tab to inspect generated OData queries
Semantic search returning no results
- Lower
SimilarityThreshold(try 0.1 instead of 0.15) - Ensure
MinSearchLengthis met (default: 3 for AI) - Verify OpenAI/Azure API key is valid
- Check browser console for API errors
AOT/Trimming build errors
- Ensure you're using the latest package version
- The component is fully AOT-compatible; if issues persist, check for conflicts with other libraries
- Use
FilterStrategy.StartsWithfor fastest filtering - Enable
Virtualize="true"for datasets > 100 items - Set appropriate
DebounceMs(300-500ms) to reduce API calls - For AI search, enable
PreWarmCache="true"to pre-generate embeddings
MIT License - see LICENSE
- GitHub Issues - Bug reports and feature requests
- GitHub Discussions - Questions and community support
Contributions are welcome! Please read our contributing guidelines before submitting a pull request.
If you find this package useful, please consider giving it a star on GitHub!
