Complete reference for Choices.js event system with detailed event types, data structures, and handling patterns for monitoring user interactions and state changes.
{ .api }
type EventType =
| 'addItem'
| 'removeItem'
| 'highlightItem'
| 'unhighlightItem'
| 'choice'
| 'change'
| 'search'
| 'showDropdown'
| 'hideDropdown'
| 'highlightChoice';{ .api }
const EVENTS = {
showDropdown: 'showDropdown',
hideDropdown: 'hideDropdown',
change: 'change',
choice: 'choice',
search: 'search',
addItem: 'addItem',
removeItem: 'removeItem',
highlightItem: 'highlightItem',
highlightChoice: 'highlightChoice',
unhighlightItem: 'unhighlightItem'
};// Listen on the original element
const element = document.querySelector('#my-select');
const choices = new Choices(element);
element.addEventListener('change', (event) => {
console.log('Selection changed:', event.detail);
});
// Or listen on choices.passedElement.element
choices.passedElement.element.addEventListener('addItem', (event) => {
console.log('Item added:', event.detail);
});// Listen to multiple events
['addItem', 'removeItem', 'change'].forEach(eventType => {
element.addEventListener(eventType, (event) => {
console.log(`Event: ${eventType}`, event.detail);
});
});Fired when the overall selection changes.
{ .api }
interface ChangeEventDetail {
value: string | string[];
}Usage:
element.addEventListener('change', (event) => {
console.log('New value:', event.detail.value);
// For single select: event.detail.value is string
// For multi-select: event.detail.value is string[]
});Example:
// Single select change
// event.detail = { value: "selected-option" }
// Multi-select change
// event.detail = { value: ["option1", "option2"] }Fired when an item is added to the selection.
{ .api }
interface AddItemEventDetail {
id: number;
value: string;
label: string;
customProperties?: Record<string, any>;
keyCode?: number;
}Usage:
element.addEventListener('addItem', (event) => {
const { id, value, label, customProperties, keyCode } = event.detail;
console.log(`Added: ${label} (${value})`);
if (customProperties) {
console.log('Custom data:', customProperties);
}
if (keyCode) {
console.log('Added via keyboard:', keyCode);
}
});Example:
// event.detail = {
// id: 123,
// value: "premium-plan",
// label: "Premium Plan",
// customProperties: { price: 29.99, features: [...] },
// keyCode: 13 // Enter key
// }Fired when an item is removed from the selection.
{ .api }
interface RemoveItemEventDetail {
id: number;
value: string;
label: string;
customProperties?: Record<string, any>;
}Usage:
element.addEventListener('removeItem', (event) => {
const { id, value, label, customProperties } = event.detail;
console.log(`Removed: ${label} (${value})`);
// Handle cleanup for custom properties
if (customProperties?.tempFile) {
cleanupTempFile(customProperties.tempFile);
}
});Fired when a choice is selected from the dropdown (before becoming an item).
{ .api }
interface ChoiceEventDetail {
choice: {
id: number;
value: string;
label: string;
customProperties?: Record<string, any>;
};
}Usage:
element.addEventListener('choice', (event) => {
const choice = event.detail.choice;
console.log('Choice selected:', choice.label);
// Can be used for analytics or validation before addition
if (choice.customProperties?.requiresConfirmation) {
if (!confirm(`Add ${choice.label}?`)) {
// Prevention would need to be handled in addItem event
}
}
});Fired when user performs a search.
{ .api }
interface SearchEventDetail {
value: string;
resultCount: number;
}Usage:
element.addEventListener('search', (event) => {
const { value, resultCount } = event.detail;
console.log(`Searched for: "${value}" - ${resultCount} results`);
// Trigger external search or analytics
if (value.length >= 3) {
trackSearchQuery(value);
}
});Fired when an item is highlighted (keyboard navigation or hover).
{ .api }
interface HighlightItemEventDetail {
id: number;
value: string;
label: string;
}Usage:
element.addEventListener('highlightItem', (event) => {
const { id, value, label } = event.detail;
console.log(`Highlighted item: ${label}`);
// Update UI or show additional information
showItemPreview(value);
});Fired when an item highlight is removed.
{ .api }
interface UnhighlightItemEventDetail {
id: number;
value: string;
label: string;
}Usage:
element.addEventListener('unhighlightItem', (event) => {
const { id, value, label } = event.detail;
console.log(`Unhighlighted item: ${label}`);
// Clean up UI
hideItemPreview();
});Fired when a choice in the dropdown is highlighted.
{ .api }
interface HighlightChoiceEventDetail {
choice: {
id: number;
value: string;
label: string;
};
}Fired when the dropdown is shown.
element.addEventListener('showDropdown', (event) => {
console.log('Dropdown opened');
// Load dynamic content, track analytics
loadAdditionalChoices();
trackDropdownOpen();
});Fired when the dropdown is hidden.
element.addEventListener('hideDropdown', (event) => {
console.log('Dropdown closed');
// Cleanup, save state
saveCurrentState();
});element.addEventListener('addItem', (event) => {
const { value } = event.detail;
// Validate new item
if (!isValidSelection(value)) {
// Remove invalid item
choices.removeActiveItemsByValue(value);
showValidationError('Invalid selection');
}
});// Sync with other components
element.addEventListener('change', (event) => {
updateRelatedFields(event.detail.value);
calculateTotal();
validateForm();
});
// Update counters
element.addEventListener('addItem', () => {
updateSelectionCounter(choices.getValue().length);
});
element.addEventListener('removeItem', () => {
updateSelectionCounter(choices.getValue().length);
});// Track user behavior
element.addEventListener('search', (event) => {
analytics.track('search_performed', {
query: event.detail.value,
results: event.detail.resultCount
});
});
element.addEventListener('choice', (event) => {
analytics.track('option_selected', {
option: event.detail.choice.value,
label: event.detail.choice.label
});
});
// Track interaction patterns
let interactionStart;
element.addEventListener('showDropdown', () => {
interactionStart = Date.now();
});
element.addEventListener('hideDropdown', () => {
const duration = Date.now() - interactionStart;
analytics.track('dropdown_interaction', { duration });
});// Load choices based on search
let searchTimeout;
element.addEventListener('search', (event) => {
const { value } = event.detail;
clearTimeout(searchTimeout);
searchTimeout = setTimeout(async () => {
if (value.length >= 2) {
const results = await searchAPI(value);
choices.setChoices(results, 'id', 'name', true);
}
}, 300);
});
// Load more choices when dropdown opens
element.addEventListener('showDropdown', async () => {
if (!hasLoadedInitialChoices) {
const initialChoices = await loadInitialChoices();
choices.setChoices(initialChoices);
hasLoadedInitialChoices = true;
}
});// Maximum selection validation
element.addEventListener('addItem', (event) => {
const currentCount = choices.getValue().length;
const maxAllowed = 3;
if (currentCount > maxAllowed) {
choices.removeActiveItemsByValue(event.detail.value);
showError(`Maximum ${maxAllowed} selections allowed`);
}
});
// Custom business logic
element.addEventListener('addItem', (event) => {
const { value, customProperties } = event.detail;
if (customProperties?.requiresPermission) {
if (!hasPermission(value)) {
choices.removeActiveItemsByValue(value);
showError('Insufficient permissions for this selection');
}
}
});// Two-way binding with framework state
element.addEventListener('change', (event) => {
// Update React state
setSelectedItems(event.detail.value);
// Update Vue data
this.selectedItems = event.detail.value;
// Update Angular model
this.selectedItems = event.detail.value;
});
// Sync with localStorage
element.addEventListener('change', (event) => {
localStorage.setItem('choices_selection', JSON.stringify(event.detail.value));
});
// Restore from localStorage
window.addEventListener('load', () => {
const saved = localStorage.getItem('choices_selection');
if (saved) {
choices.setValue(JSON.parse(saved));
}
});// Announce changes to screen readers
element.addEventListener('addItem', (event) => {
announceToScreenReader(`Added ${event.detail.label}`);
});
element.addEventListener('removeItem', (event) => {
announceToScreenReader(`Removed ${event.detail.label}`);
});
function announceToScreenReader(message) {
const announcement = document.createElement('div');
announcement.setAttribute('aria-live', 'polite');
announcement.setAttribute('aria-atomic', 'true');
announcement.className = 'sr-only';
announcement.textContent = message;
document.body.appendChild(announcement);
setTimeout(() => document.body.removeChild(announcement), 1000);
}// Handle event errors gracefully
element.addEventListener('addItem', (event) => {
try {
processNewItem(event.detail);
} catch (error) {
console.error('Error processing new item:', error);
choices.removeActiveItemsByValue(event.detail.value);
showUserError('Failed to add item. Please try again.');
}
});
// Validate event data
element.addEventListener('change', (event) => {
if (!event.detail || typeof event.detail.value === 'undefined') {
console.warn('Invalid change event data:', event.detail);
return;
}
handleValidChange(event.detail.value);
});Note: Choices.js events cannot be prevented using preventDefault() as they are custom events fired after actions complete. To prevent actions, handle them in the event and reverse the action:
// "Prevent" item addition by immediately removing it
element.addEventListener('addItem', (event) => {
if (shouldPreventAddition(event.detail.value)) {
choices.removeActiveItemsByValue(event.detail.value);
}
});This comprehensive event system enables deep integration with application logic, validation, analytics, and user experience enhancements.