Choices.js provides a comprehensive template system that allows complete customization of the HTML structure and styling. Templates are functions that return HTML elements, giving you full control over how each component renders.
The main templates interface defining all customizable HTML components.
interface Templates {
containerOuter: (templateOptions: TemplateOptions, dir: string, isSelectElement: boolean, isSelectOneElement: boolean, searchEnabled: boolean, passedElementType: PassedElementType, labelId: string) => HTMLElement;
containerInner: (templateOptions: TemplateOptions) => HTMLElement;
itemList: (templateOptions: TemplateOptions, isSelectOneElement: boolean) => HTMLElement;
placeholder: (templateOptions: TemplateOptions, value: string) => HTMLElement;
item: (templateOptions: TemplateOptions, data: ChoiceFull, removeItemButton: boolean, removeItemButtonAlignLeft: boolean, removeItemText: string) => HTMLElement;
choiceList: (templateOptions: TemplateOptions, isSelectOneElement: boolean) => HTMLElement;
choiceGroup: (templateOptions: TemplateOptions, data: GroupFull) => HTMLElement;
choice: (templateOptions: TemplateOptions, data: ChoiceFull, itemSelectText: string, groupName?: string) => HTMLElement;
input: (templateOptions: TemplateOptions, placeholderValue: string | null, searchEnabled: boolean) => HTMLElement;
dropdown: (templateOptions: TemplateOptions) => HTMLElement;
notice: (templateOptions: TemplateOptions, label: string, type: NoticeType) => HTMLElement;
}
interface TemplateOptions {
classNames: ClassNames;
searchEnabled: boolean;
}
type CallbackOnCreateTemplatesFn = (template: (html: string) => HTMLElement) => Partial<Templates>;Templates for the main container structure that wraps the entire Choices interface.
/**
* Creates the outer container element that wraps the entire Choices interface
* @param templateOptions - Template configuration including classNames and searchEnabled
* @param dir - Text direction ('ltr' or 'rtl')
* @param isSelectElement - Whether the original element is a select
* @param isSelectOneElement - Whether it's a single-select element
* @param searchEnabled - Whether search functionality is enabled
* @param passedElementType - Type of the original element ('text', 'select-one', 'select-multiple')
* @param labelId - ID of associated label element for accessibility
*/
containerOuter: (
templateOptions: TemplateOptions,
dir: string,
isSelectElement: boolean,
isSelectOneElement: boolean,
searchEnabled: boolean,
passedElementType: PassedElementType,
labelId: string
) => HTMLElement;
/**
* Creates the inner container that holds the items and input
* @param templateOptions - Template configuration including classNames and searchEnabled
*/
containerInner: (templateOptions: TemplateOptions) => HTMLElement;Usage Examples:
const choices = new Choices('#my-select', {
callbackOnCreateTemplates: (template) => {
return {
containerOuter: (classNames, dir, isSelectElement, isSelectOneElement, searchEnabled, passedElementType, labelId) => {
return template(`
<div class="${classNames.containerOuter.join(' ')} custom-container"
dir="${dir}"
data-type="${passedElementType}"
${labelId ? `aria-labelledby="${labelId}"` : ''}>
</div>
`);
},
containerInner: (classNames) => {
return template(`
<div class="${classNames.containerInner.join(' ')} custom-inner">
</div>
`);
}
};
}
});Templates for rendering selected items and the item list container.
/**
* Creates the container for selected items
* @param classNames - CSS class configuration object
* @param isSelectOneElement - Whether it's a single-select element
*/
itemList: (classNames: ClassNames, isSelectOneElement: boolean) => HTMLElement;
/**
* Creates individual selected item elements
* @param classNames - CSS class configuration object
* @param data - Item data including value, label, id, etc.
* @param removeItemButton - Whether to show remove button
* @param removeItemButtonAlignLeft - Whether to align remove button left
* @param removeItemText - Accessibility text for remove button
*/
item: (
classNames: ClassNames,
data: {
id: number;
value: string;
label: string;
active?: boolean;
disabled?: boolean;
highlighted?: boolean;
placeholder?: boolean;
labelClass?: string;
labelDescription?: string;
},
removeItemButton: boolean,
removeItemButtonAlignLeft: boolean,
removeItemText: string
) => HTMLElement;
/**
* Creates placeholder element for empty states
* @param classNames - CSS class configuration object
* @param value - Placeholder text value
*/
placeholder: (classNames: ClassNames, value: string) => HTMLElement;Usage Examples:
const choices = new Choices('#my-select', {
callbackOnCreateTemplates: (template) => {
return {
item: (classNames, data, removeItemButton, removeItemButtonAlignLeft, removeItemText) => {
const itemElement = template(`
<div class="${classNames.item.join(' ')} custom-item"
data-item
data-id="${data.id}"
data-value="${data.value}"
${data.active ? 'aria-selected="true"' : ''}
${data.disabled ? 'aria-disabled="true"' : ''}>
<span class="item-label">${data.label}</span>
${data.labelDescription ? `<small class="item-description">${data.labelDescription}</small>` : ''}
${removeItemButton ? `<button type="button" class="remove-item" aria-label="${removeItemText}">×</button>` : ''}
</div>
`);
return itemElement;
},
placeholder: (classNames, value) => {
return template(`
<div class="${classNames.placeholder.join(' ')} custom-placeholder">
<i class="placeholder-icon"></i>
<span>${value}</span>
</div>
`);
}
};
}
});Templates for rendering available choices in the dropdown.
/**
* Creates the dropdown container for available choices
* @param classNames - CSS class configuration object
* @param isSelectOneElement - Whether it's a single-select element
*/
choiceList: (classNames: ClassNames, isSelectOneElement: boolean) => HTMLElement;
/**
* Creates group headers for grouped choices
* @param classNames - CSS class configuration object
* @param data - Group data including id, value, label, disabled state
*/
choiceGroup: (
classNames: ClassNames,
data: {
id: number;
value: string;
label: string;
disabled: boolean;
}
) => HTMLElement;
/**
* Creates individual choice elements in the dropdown
* @param classNames - CSS class configuration object
* @param data - Choice data including value, label, selected state, etc.
* @param itemSelectText - Accessibility text for selection
* @param groupName - Name of parent group if applicable
*/
choice: (
classNames: ClassNames,
data: {
id: number;
value: string;
label: string;
disabled?: boolean;
selected?: boolean;
placeholder?: boolean;
labelClass?: string;
labelDescription?: string;
},
itemSelectText: string,
groupName?: string
) => HTMLElement;Usage Examples:
const choices = new Choices('#my-select', {
callbackOnCreateTemplates: (template) => {
return {
choice: (classNames, data, itemSelectText, groupName) => {
return template(`
<div class="${classNames.itemChoice.join(' ')} custom-choice"
data-select-text="${itemSelectText}"
data-choice
data-id="${data.id}"
data-value="${data.value}"
${data.groupId ? `data-group-id="${data.groupId}"` : ''}
${data.disabled ? 'data-choice-disabled aria-disabled="true"' : 'data-choice-selectable'}
${data.selected ? 'aria-selected="true"' : ''}
role="option">
<div class="choice-content">
<span class="choice-label">${data.label}</span>
${data.labelDescription ? `<small class="choice-description">${data.labelDescription}</small>` : ''}
${groupName ? `<span class="choice-group">${groupName}</span>` : ''}
</div>
${data.selected ? '<span class="choice-selected-icon">✓</span>' : ''}
</div>
`);
},
choiceGroup: (classNames, data) => {
return template(`
<div class="${classNames.group.join(' ')} custom-group"
data-group
data-id="${data.id}"
data-value="${data.value}"
${data.disabled ? 'aria-disabled="true"' : ''}
role="group">
<div class="${classNames.groupHeading.join(' ')} custom-group-heading"
aria-hidden="true">
${data.label}
</div>
</div>
`);
}
};
}
});Templates for the search input and dropdown container.
/**
* Creates the search/input element
* @param classNames - CSS class configuration object
* @param placeholderValue - Placeholder text for the input
* @param searchEnabled - Whether search functionality is enabled
*/
input: (
classNames: ClassNames,
placeholderValue: string | null,
searchEnabled: boolean
) => HTMLElement;
/**
* Creates the dropdown container element
* @param classNames - CSS class configuration object
*/
dropdown: (classNames: ClassNames) => HTMLElement;Templates for displaying informational messages and states.
/**
* Creates notice elements for empty states and messages
* @param classNames - CSS class configuration object
* @param label - Notice text content
* @param type - Type of notice ('no-results', 'no-choices', etc.)
*/
notice: (
classNames: ClassNames,
label: string,
type: NoticeType
) => HTMLElement;
type NoticeType = 'noResults' | 'noChoices' | 'addChoice';Usage Examples:
const choices = new Choices('#my-select', {
callbackOnCreateTemplates: (template) => {
return {
input: (classNames, placeholderValue, searchEnabled) => {
return template(`
<input type="${searchEnabled ? 'search' : 'text'}"
class="${classNames.input.join(' ')} custom-input"
autocomplete="off"
autocapitalize="off"
spellcheck="false"
${placeholderValue ? `placeholder="${placeholderValue}"` : ''}
${searchEnabled ? 'role="combobox" aria-autocomplete="list"' : ''}>
`);
},
notice: (classNames, label, type) => {
const noticeClass = type === 'noResults' ? 'no-results' :
type === 'noChoices' ? 'no-choices' : 'add-choice';
return template(`
<div class="${classNames.notice.join(' ')} custom-notice custom-notice--${noticeClass}"
role="status"
aria-live="polite">
<span class="notice-icon"></span>
<span class="notice-text">${label}</span>
</div>
`);
}
};
}
});You can extend or modify default templates by accessing them through the static defaults property.
const choices = new Choices('#my-select', {
callbackOnCreateTemplates: (template) => {
const defaultTemplates = Choices.defaults.templates;
return {
// Use default template but add custom attributes
item: (classNames, data, removeItemButton, removeItemButtonAlignLeft, removeItemText) => {
const defaultItem = defaultTemplates.item(classNames, data, removeItemButton, removeItemButtonAlignLeft, removeItemText);
defaultItem.setAttribute('data-custom', 'true');
defaultItem.style.borderRadius = '8px';
return defaultItem;
}
};
}
});Utility functions for common template customization patterns.
// Helper for creating accessible elements
const createAccessibleElement = (template, tagName, classNames, content, ariaProps = {}) => {
const ariaAttributes = Object.entries(ariaProps)
.map(([key, value]) => `${key}="${value}"`)
.join(' ');
return template(`
<${tagName} class="${classNames.join(' ')}" ${ariaAttributes}>
${content}
</${tagName}>
`);
};
// Helper for icon integration
const withIcon = (template, html, iconName) => {
return template(`
<span class="icon icon-${iconName}"></span>
${html}
`);
};
const choices = new Choices('#my-select', {
callbackOnCreateTemplates: (template) => {
return {
choice: (classNames, data, itemSelectText, groupName) => {
const content = `
<span class="choice-label">${data.label}</span>
${data.labelDescription ? `<small>${data.labelDescription}</small>` : ''}
`;
return createAccessibleElement(
template,
'div',
[...classNames.itemChoice, 'custom-choice'],
withIcon(template, content, 'check'),
{
'role': 'option',
'data-choice': '',
'data-id': data.id,
'data-value': data.value,
'aria-selected': data.selected ? 'true' : 'false'
}
);
}
};
}
});Templates work closely with CSS classes defined in the classNames configuration:
const choices = new Choices('#my-select', {
classNames: {
containerOuter: ['choices', 'custom-choices'],
item: ['choices__item', 'custom-item'],
itemChoice: ['choices__item--choice', 'custom-choice'],
// ... other class names
},
callbackOnCreateTemplates: (template) => {
return {
item: (classNames, data) => {
// classNames.item will contain ['choices__item', 'custom-item']
return template(`
<div class="${classNames.item.join(' ')}" data-item>
${data.label}
</div>
`);
}
};
}
});This approach ensures templates and CSS work together seamlessly while allowing full customization of both structure and appearance.