Complete guide to the Choices.js template system for customizing HTML generation and UI appearance through the 12 template functions.
Choices.js uses a modular template system where each UI component is generated by a dedicated template function. These functions can be customized to modify appearance, structure, and behavior.
{ .api }
interface Templates {
containerOuter: Function;
containerInner: Function;
itemList: Function;
placeholder: Function;
item: Function;
choiceList: Function;
choiceGroup: Function;
choice: Function;
input: Function;
dropdown: Function;
notice: Function;
option: Function;
}const choices = new Choices('#select', {
callbackOnCreateTemplates: function(template) {
return {
// Keep default templates
...template,
// Override specific templates
item: (classNames, data) => {
return `<div class="${classNames.item} custom-item">
${data.label}
<button class="remove-btn">×</button>
</div>`;
},
choice: (classNames, data) => {
return `<div class="${classNames.item} custom-choice" data-value="${data.value}">
<span class="choice-label">${data.label}</span>
${data.customProperties?.badge ? `<span class="badge">${data.customProperties.badge}</span>` : ''}
</div>`;
}
};
}
});import { templates } from 'choices.js';
// Create custom templates based on defaults
const customTemplates = {
...templates,
item: (classNames, data) => `<custom-item>${data.label}</custom-item>`
};
const choices = new Choices('#select', {
callbackOnCreateTemplates: () => customTemplates
});Creates the main wrapper element for the entire component.
{ .api }
/**
* @param classNames - CSS class configuration
* @param dir - Text direction ('ltr' or 'rtl')
* @param isSelectElement - Whether original element was <select>
* @param isSelectOneElement - Whether it's single select
* @param searchEnabled - Whether search is enabled
* @param passedElementType - Type of original element
* @returns {HTMLElement} - Main container element
*/
containerOuter(
classNames: ClassNames,
dir: string,
isSelectElement: boolean,
isSelectOneElement: boolean,
searchEnabled: boolean,
passedElementType: string
): HTMLElementDefault Implementation:
containerOuter: (classNames, dir, isSelectElement, isSelectOneElement, searchEnabled, passedElementType) => {
const div = document.createElement('div');
div.className = classNames.containerOuter;
div.dataset.type = passedElementType;
if (dir) {
div.dir = dir;
}
if (isSelectOneElement) {
div.tabIndex = 0;
}
return div;
}Custom Example:
containerOuter: (classNames, dir, isSelectElement, isSelectOneElement, searchEnabled, passedElementType) => {
return `
<div class="${classNames.containerOuter} my-custom-container"
data-type="${passedElementType}"
data-search="${searchEnabled}"
dir="${dir || 'ltr'}"
${isSelectOneElement ? 'tabindex="0"' : ''}>
</div>
`;
}Creates the inner container that holds the input and selected items.
{ .api }
/**
* @param classNames - CSS class configuration
* @returns {HTMLElement} - Inner container element
*/
containerInner(classNames: ClassNames): HTMLElementCustom Example:
containerInner: (classNames) => {
return `<div class="${classNames.containerInner} inner-wrapper"></div>`;
}Creates the container for selected items.
{ .api }
/**
* @param classNames - CSS class configuration
* @param isSelectOneElement - Whether it's single select
* @returns {HTMLElement} - Item list container
*/
itemList(classNames: ClassNames, isSelectOneElement: boolean): HTMLElementCustom Example:
itemList: (classNames, isSelectOneElement) => {
return `
<div class="${classNames.list} ${isSelectOneElement ? classNames.listSingle : classNames.listItems}">
${!isSelectOneElement ? '<div class="item-list-header">Selected:</div>' : ''}
</div>
`;
}Creates placeholder text element.
{ .api }
/**
* @param classNames - CSS class configuration
* @param value - Placeholder text
* @returns {HTMLElement} - Placeholder element
*/
placeholder(classNames: ClassNames, value: string): HTMLElementCustom Example:
placeholder: (classNames, value) => {
return `
<div class="${classNames.placeholder} custom-placeholder">
<span class="placeholder-icon">🔍</span>
<span class="placeholder-text">${value}</span>
</div>
`;
}Creates individual selected item elements.
{ .api }
/**
* @param classNames - CSS class configuration
* @param data - Item data object
* @param removeItemButton - Whether to show remove button
* @returns {HTMLElement} - Item element
*/
item(classNames: ClassNames, data: Item, removeItemButton: boolean): HTMLElementItem Data Structure:
{ .api }
interface ItemData {
id: number;
value: string;
label: string;
highlighted: boolean;
customProperties?: Record<string, any>;
}Custom Example:
item: (classNames, data, removeItemButton) => {
const customClass = data.customProperties?.cssClass || '';
const icon = data.customProperties?.icon || '';
return `
<div class="${classNames.item} ${customClass} ${data.highlighted ? classNames.highlightedState : ''}"
data-item data-id="${data.id}" data-value="${data.value}">
${icon ? `<span class="item-icon">${icon}</span>` : ''}
<span class="item-label">${data.label}</span>
${data.customProperties?.metadata ? `<small class="item-meta">${data.customProperties.metadata}</small>` : ''}
${removeItemButton ? `<button type="button" class="${classNames.button}" data-button>Remove</button>` : ''}
</div>
`;
}Creates the dropdown container for available choices.
{ .api }
/**
* @param classNames - CSS class configuration
* @param isSelectOneElement - Whether it's single select
* @returns {HTMLElement} - Choice list container
*/
choiceList(classNames: ClassNames, isSelectOneElement: boolean): HTMLElementCreates group headers in the dropdown.
{ .api }
/**
* @param classNames - CSS class configuration
* @param data - Group data object
* @returns {HTMLElement} - Group element
*/
choiceGroup(classNames: ClassNames, data: Group): HTMLElementGroup Data Structure:
{ .api }
interface GroupData {
id: number;
value: string;
active: boolean;
disabled: boolean;
}Custom Example:
choiceGroup: (classNames, data) => {
return `
<div class="${classNames.group} ${data.disabled ? classNames.itemDisabled : ''}"
data-group data-id="${data.id}" data-value="${data.value}">
<div class="group-header">
<span class="group-title">${data.value}</span>
<span class="group-indicator">▼</span>
</div>
</div>
`;
}Creates individual choice elements in the dropdown.
{ .api }
/**
* @param classNames - CSS class configuration
* @param data - Choice data object
* @param itemSelectText - Screen reader text
* @returns {HTMLElement} - Choice element
*/
choice(classNames: ClassNames, data: Choice, itemSelectText: string): HTMLElementChoice Data Structure:
{ .api }
interface ChoiceData {
id: number;
value: string;
label: string;
disabled: boolean;
selected: boolean;
customProperties?: Record<string, any>;
}Custom Example:
choice: (classNames, data, itemSelectText) => {
const customClass = data.customProperties?.cssClass || '';
const description = data.customProperties?.description || '';
const price = data.customProperties?.price;
return `
<div class="${classNames.item} ${classNames.itemChoice} ${customClass} ${data.disabled ? classNames.itemDisabled : classNames.itemSelectable}"
data-select-text="${itemSelectText}"
data-choice ${data.disabled ? 'data-choice-disabled' : 'data-choice-selectable'}
data-id="${data.id}"
data-value="${data.value}"
${data.groupId > 0 ? `data-group-id="${data.groupId}"` : ''}>
<div class="choice-content">
<div class="choice-main">
<span class="choice-label">${data.label}</span>
${price ? `<span class="choice-price">$${price}</span>` : ''}
</div>
${description ? `<div class="choice-description">${description}</div>` : ''}
</div>
</div>
`;
}Creates the input/search element.
{ .api }
/**
* @param classNames - CSS class configuration
* @param placeholderValue - Placeholder text
* @returns {HTMLElement} - Input element
*/
input(classNames: ClassNames, placeholderValue: string): HTMLElementCustom Example:
input: (classNames, placeholderValue) => {
return `
<input type="search"
class="${classNames.input} custom-search-input"
placeholder="${placeholderValue}"
autocomplete="off"
autocapitalize="off"
spellcheck="false">
`;
}Creates the dropdown container.
{ .api }
/**
* @param classNames - CSS class configuration
* @returns {HTMLElement} - Dropdown container
*/
dropdown(classNames: ClassNames): HTMLElementCreates notice/error message elements.
{ .api }
/**
* @param classNames - CSS class configuration
* @param innerText - Notice text
* @param type - Notice type ('error', 'success', etc.)
* @returns {HTMLElement} - Notice element
*/
notice(classNames: ClassNames, innerText: string, type: string): HTMLElementCustom Example:
notice: (classNames, innerText, type) => {
const iconMap = {
error: '⚠️',
success: '✅',
info: 'ℹ️'
};
return `
<div class="${classNames.notice} notice-${type}">
<span class="notice-icon">${iconMap[type] || '📄'}</span>
<span class="notice-text">${innerText}</span>
</div>
`;
}Creates <option> elements for the original <select> (accessibility).
{ .api }
/**
* @param data - Option data object
* @returns {HTMLElement} - Option element
*/
option(data: Choice): HTMLElementcallbackOnCreateTemplates: function(template) {
return {
...template,
item: (classNames, data, removeItemButton) => {
// Different templates based on data
if (data.customProperties?.type === 'premium') {
return `<div class="${classNames.item} premium-item">⭐ ${data.label}</div>`;
}
if (data.customProperties?.urgent) {
return `<div class="${classNames.item} urgent-item">🚨 ${data.label}</div>`;
}
// Default template
return template.item(classNames, data, removeItemButton);
}
};
}choice: (classNames, data, itemSelectText) => {
const { customProperties } = data;
return `
<div class="${classNames.item} ${classNames.itemChoice} rich-choice"
data-choice data-id="${data.id}" data-value="${data.value}">
<div class="choice-media">
${customProperties?.avatar ? `<img src="${customProperties.avatar}" class="choice-avatar">` : ''}
</div>
<div class="choice-content">
<div class="choice-title">${data.label}</div>
${customProperties?.subtitle ? `<div class="choice-subtitle">${customProperties.subtitle}</div>` : ''}
${customProperties?.tags ? `<div class="choice-tags">${customProperties.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}</div>` : ''}
</div>
<div class="choice-actions">
${customProperties?.actionButton ? `<button type="button" class="choice-action">${customProperties.actionButton}</button>` : ''}
</div>
</div>
`;
}callbackOnCreateTemplates: function(template) {
const config = this.config;
return {
...template,
item: (classNames, data, removeItemButton) => {
// Use different templates based on configuration
if (config.customItemRenderer) {
return config.customItemRenderer(classNames, data, removeItemButton);
}
return template.item(classNames, data, removeItemButton);
}
};
}// Note: Event handlers should be added after DOM insertion
callbackOnCreateTemplates: function(template) {
const self = this;
return {
...template,
item: (classNames, data, removeItemButton) => {
const element = template.item(classNames, data, removeItemButton);
// Add custom event handling after insertion
setTimeout(() => {
const itemElement = document.querySelector(`[data-id="${data.id}"]`);
if (itemElement) {
itemElement.addEventListener('click', (e) => {
if (e.target.classList.contains('custom-action')) {
handleCustomAction(data);
}
});
}
}, 0);
return element;
}
};
}function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Use in templates
item: (classNames, data, removeItemButton) => {
const safeLabel = escapeHtml(data.label);
return `<div class="${classNames.item}">${safeLabel}</div>`;
}const templateHelpers = {
renderIcon(iconName) {
const icons = {
star: '⭐',
warning: '⚠️',
check: '✅'
};
return icons[iconName] || '';
},
formatPrice(price) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(price);
},
truncate(text, length = 50) {
return text.length > length ? text.substring(0, length) + '...' : text;
}
};
// Use helpers in templates
choice: (classNames, data, itemSelectText) => {
const icon = templateHelpers.renderIcon(data.customProperties?.icon);
const price = data.customProperties?.price ? templateHelpers.formatPrice(data.customProperties.price) : '';
return `
<div class="${classNames.item} ${classNames.itemChoice}">
${icon} ${data.label} ${price}
</div>
`;
}const templateCache = new Map();
callbackOnCreateTemplates: function(template) {
return {
...template,
choice: (classNames, data, itemSelectText) => {
const cacheKey = `${data.id}-${data.label}-${JSON.stringify(data.customProperties)}`;
if (templateCache.has(cacheKey)) {
return templateCache.get(cacheKey);
}
const result = generateComplexTemplate(classNames, data, itemSelectText);
templateCache.set(cacheKey, result);
return result;
}
};
}// Efficient string templates over DOM manipulation
choice: (classNames, data, itemSelectText) => {
// Good: String concatenation
return `<div class="${classNames.item}">${data.label}</div>`;
// Avoid: DOM manipulation in templates
// const div = document.createElement('div');
// div.className = classNames.item;
// div.textContent = data.label;
// return div;
}This comprehensive template system allows complete customization of Choices.js appearance and behavior while maintaining performance and accessibility.