Autocomplete
The Autocomplete component enhances text inputs with intelligent suggestion dropdowns. It provides real-time filtering, keyboard navigation, and support for both static and async data sources. Perfect for search boxes, address inputs, tag selection, and any scenario where users need suggestions while typing.
Key Features
- Flexible Data Sources - Static arrays or async functions (API calls)
- Smart Filtering - Client-side and server-side filtering with debouncing
- Keyboard Navigation - Full arrow key, Enter, Escape, Tab support
- Match Highlighting - Highlights matched text in suggestions
- Position Detection - Auto-adjusts dropdown position to viewport
- Loading States - Shows loading indicators for async operations
- Accessibility - ARIA compliant with screen reader support
When to Use
- Search inputs with suggested queries
- Address or location lookups
- Product or category selection
- Username or email input with suggestions
- Any input that benefits from auto-suggestions
Basic Usage
// Static data source
const autocomplete = Domma.elements.autocomplete('#search', {
data: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'],
minChars: 1,
onSelect: (value) => console.log('Selected:', value)
});
// Async data source
Domma.elements.autocomplete('#city', {
dataSource: async (query) => {
const res = await fetch(`/api/cities?q=${query}`);
return res.json();
},
debounce: 300
});
Quick Start
Simple Autocomplete
Type to see fruit suggestions:
<input type="text" id="fruit-search" placeholder="Type a fruit name...">
<script>
const fruits = [
'Apple', 'Apricot', 'Avocado', 'Banana', 'Blackberry', 'Blueberry',
'Cherry', 'Coconut', 'Date', 'Dragonfruit', 'Elderberry', 'Fig',
'Grape', 'Grapefruit', 'Guava', 'Kiwi', 'Lemon', 'Lime', 'Mango',
'Orange', 'Papaya', 'Peach', 'Pear', 'Pineapple', 'Plum', 'Raspberry',
'Strawberry', 'Tangerine', 'Watermelon'
];
Domma.elements.autocomplete('#fruit-search', {
data: fruits,
minChars: 1,
maxResults: 8,
onSelect: (value) => {
console.log('Selected fruit:', value);
}
});
</script>
Tutorial
Let's build a country selector with flag emojis and match highlighting.
Step 1: Create the Input
<input type="text" id="country-search" placeholder="Search countries...">
Step 2: Prepare Data with Labels and Values
const countries = [
{ value: 'us', label: '\ud83c\uddfa\ud83c\uddf8 United States' },
{ value: 'uk', label: '\ud83c\uddec\ud83c\udde7 United Kingdom' },
{ value: 'ca', label: '\ud83c\udde8\ud83c\udde6 Canada' },
{ value: 'au', label: '\ud83c\udde6\ud83c\uddfa Australia' },
{ value: 'de', label: '\ud83c\udde9\ud83c\uddea Germany' },
{ value: 'fr', label: '\ud83c\uddeb\ud83c\uddf7 France' },
{ value: 'jp', label: '\ud83c\uddef\ud83c\uddf5 Japan'},
{ value: 'cn', label: '\ud83c\udde8\ud83c\uddf3 China' }
// ... more countries
];
Step 3: Initialize Autocomplete
const countryPicker = Domma.elements.autocomplete('#country-search', {
data: countries,
minChars: 1,
highlightMatches: true,
onSelect: (country) => {
console.log('Selected:', country.label);
console.log('Country code:', country.value);
}
});
Step 4: Try It Out
Select a country to see the result
Key Points: The autocomplete filters on both values and labels, highlights matched text, and returns the full object when an item is selected.
Examples
Example 1: Async Data Source (Simulated API)
Search for programming languages with simulated async fetch:
Notice the loading indicator while fetching results
Domma.elements.autocomplete('#language-search', {
dataSource: async (query) => {
// Simulate API call with delay
await new Promise(resolve => setTimeout(resolve, 500));
const languages = [
'JavaScript', 'TypeScript', 'Python', 'Java', 'C++', 'C#',
'Ruby', 'Go', 'Rust', 'PHP', 'Swift', 'Kotlin'
];
return languages.filter(lang =>
lang.toLowerCase().includes(query.toLowerCase())
);
},
debounce: 300,
minChars: 2,
loadingMessage: 'Searching languages...'
});
Example 2: Custom Rendering
Render suggestions with custom HTML structure:
const users = [
{ name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin' },
{ name: 'Bob Smith', email: 'bob@example.com', role: 'User' },
{ name: 'Carol White', email: 'carol@example.com', role: 'Editor' }
];
Domma.elements.autocomplete('#user-search', {
data: users,
renderItem: (user, query) => {
return `
<div style="display: flex; flex-direction: column;">
<strong>${user.name}</strong>
<span style="font-size: 0.875rem; color: #666;">${user.email}</span>
<span style="font-size: 0.75rem; color: #999;">Role: ${user.role}</span>
</div>
`;
},
onSelect: (user) => console.log('Selected user:', user.name)
});
Example 3: Position Control
Control dropdown position (try scrolling):
// Force above
Domma.elements.autocomplete('#input-above', {
data: items,
position: 'above'
});
// Auto-detect (default)
Domma.elements.autocomplete('#input-auto', {
data: items,
position: 'auto' // Default - chooses best position
});
Example 4: Clear on Select
Useful for multi-entry inputs (tags, emails, etc.):
const tags = ['React', 'Vue', 'Angular', 'Svelte', 'Next.js', 'Nuxt.js'];
const selectedTags = [];
Domma.elements.autocomplete('#tag-input', {
data: tags,
clearOnSelect: true,
onSelect: (tag) => {
if (!selectedTags.includes(tag)) {
selectedTags.push(tag);
renderTags();
}
}
});
function renderTags() {
document.getElementById('tag-container').innerHTML =
selectedTags.map(tag =>
`<span class="badge">${tag}</span>`
).join('');
}
Best Practices
Accessibility
- ARIA Attributes: Autocomplete includes proper ARIA roles and attributes automatically
- Keyboard Navigation: Always test with keyboard only (Tab, Arrow keys, Enter, Escape)
- Screen Readers: Results are announced as users navigate with arrow keys
- Label Elements: Always use
<label>elements for inputs
Performance
- Debouncing: Use appropriate debounce values (200-500ms) for async data sources
- Result Limits: Set
maxResultsto limit dropdown size (recommended: 8-12) - Large Datasets: For 1000+ items, use async data source with server-side filtering
- Caching: Cache API responses when appropriate to reduce network requests
UX Guidelines
- Minimum Characters: Set
minCharsto 2-3 to avoid showing too many results - Empty States: Provide helpful empty messages ("No results found", "Type to search")
- Loading Indicators: Always show loading states for async operations
- Match Highlighting: Enable
highlightMatchesto help users see why items matched
Common Pitfalls
- Avoid: Setting
minChars: 0- this can show entire dataset on focus - Avoid: Very short debounce times (<100ms) - causes excessive API calls
- Avoid: Very long debounce times (>800ms) - feels sluggish to users
- Avoid: Not handling errors in async data sources - always use try/catch
API Reference
Options
| Option | Type | Default | Description |
|---|---|---|---|
data |
Array | [] |
Static data source (strings or objects with label/value) |
dataSource |
Function | null |
Async function that returns Promise<Array> |
minChars |
Number | 1 |
Minimum characters before showing suggestions |
maxResults |
Number | 10 |
Maximum number of results to display |
debounce |
Number | 300 |
Debounce delay in milliseconds |
filterFn |
Function | null |
Custom filter function (item, query) => boolean |
renderItem |
Function | null |
Custom item renderer (item, query) => HTML string or Element |
highlightMatches |
Boolean | true |
Highlight matched text in results |
position |
String | 'auto' |
Dropdown position: 'auto', 'above', 'below' |
placeholder |
String | '' |
Input placeholder text |
emptyMessage |
String | 'No results found' |
Message shown when no results |
loadingMessage |
String | 'Loading...' |
Message shown while loading async data |
caseSensitive |
Boolean | false |
Case-sensitive filtering |
selectOnEnter |
Boolean | true |
Select active item on Enter key |
clearOnSelect |
Boolean | false |
Clear input after selection |
onSelect |
Function | null |
Callback when item selected: (value, event) => {} |
onChange |
Function | null |
Callback on input change: (value, event) => {} |
onOpen |
Function | null |
Callback when dropdown opens: () => {} |
onClose |
Function | null |
Callback when dropdown closes: () => {} |
onFilter |
Function | null |
Transform filtered results: (results, query) => results |
Methods
| Method | Returns | Description |
|---|---|---|
open() |
this | Open the dropdown |
close() |
this | Close the dropdown |
toggle() |
this | Toggle dropdown open/close |
isOpen() |
Boolean | Check if dropdown is open |
setValue(value) |
this | Set input value |
getValue() |
String | Get current input value |
setData(data) |
this | Update static data source |
refresh() |
this | Re-filter with current input value |
clearValue() |
this | Clear input and close dropdown |
focus() |
this | Focus the input element |
destroy() |
void | Remove component and clean up |
CSS Classes
| Class | Description |
|---|---|
.dm-autocomplete |
Wrapper container |
.dm-autocomplete-input |
The input element |
.dm-autocomplete-dropdown |
Dropdown container |
.dm-autocomplete-list |
Results list (ul) |
.dm-autocomplete-item |
Individual result item (li) |
.dm-autocomplete-item.active |
Currently focused item |
.dm-autocomplete-match |
Highlighted match text |
.dm-autocomplete-loading |
Loading indicator |
.dm-autocomplete-empty |
Empty state message |
Declarative Configuration
Use Domma's declarative configuration system to initialize autocomplete components from JSON config.
Basic Configuration
$.setup({
'#product-search': {
component: 'autocomplete',
options: {
data: ['Laptop', 'Mouse', 'Keyboard', 'Monitor', 'Webcam'],
minChars: 2,
highlightMatches: true
}
}
});
With Callbacks
$.setup({
'#search-input': {
component: 'autocomplete',
options: {
dataSource: async (query) => {
const response = await fetch(`/api/search?q=${query}`);
return response.json();
},
debounce: 400,
onSelect: (item) => {
console.log('User selected:', item);
}
}
}
});
Multiple Instances
$.setup({
'#fruit-search': {
component: 'autocomplete',
options: {
data: ['Apple', 'Banana', 'Cherry'],
placeholder: 'Search fruits...'
}
},
'#color-search': {
component: 'autocomplete',
options: {
data: ['Red', 'Blue', 'Green', 'Yellow'],
placeholder: 'Search colors...',
clearOnSelect: true
}
}
});
Model Integration
Autocomplete integrates seamlessly with Domma's reactive Model system (M) for two-way data binding. When bound to a model, the autocomplete automatically syncs with model changes and updates the model when user input changes.
Basic Model Binding
// Create a model with a search field
const searchModel = M.create({
query: { type: 'string', default: '' }
});
// Bind autocomplete to model
Domma.elements.autocomplete('#search', {
data: ['Apple', 'Banana', 'Cherry', 'Date'],
model: searchModel,
modelKey: 'query'
});
// Model changes update the input
searchModel.set('query', 'App'); // Input shows "App"
// Typing updates the model automatically
// Display model value elsewhere using M.bind()
M.bind(searchModel, 'query', '#model-display');
With Config Engine
const userModel = M.create({
searchTerm: { type: 'string', default: '' }
});
$.setup({
'#search': {
component: 'autocomplete',
options: {
data: searchData,
model: userModel,
modelKey: 'searchTerm'
}
}
});
Multiple Bindings
When multiple elements bind to the same model field, they all stay in sync automatically:
const model = M.create({
query: { type: 'string', default: '' }
});
// Autocomplete bound to model
Domma.elements.autocomplete('#search-1', {
data: items,
model: model,
modelKey: 'query'
});
// Display the value elsewhere
M.bind(model, 'query', '#search-display');
// Another input also bound to same field
M.bind(model, 'query', '#search-mirror', { twoWay: true });
// All three stay synchronized!
Related Elements
Explore related Domma components:
Coming Soon: Pillbox component for multi-select tag inputs