or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdcore-usage.mddata-management.mdevents.mdindex.mdtemplates.mdtypes.md
tile.json

templates.mddocs/

Templates

Complete guide to the Choices.js template system for customizing HTML generation and UI appearance through the 12 template functions.

Template System Overview

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;
}

Template Customization

Using callbackOnCreateTemplates

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>`;
      }
    };
  }
});

Direct Template Override

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
});

Template Functions Reference

containerOuter Template

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
): HTMLElement

Default 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>
  `;
}

containerInner Template

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): HTMLElement

Custom Example:

containerInner: (classNames) => {
  return `<div class="${classNames.containerInner} inner-wrapper"></div>`;
}

itemList Template

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): HTMLElement

Custom Example:

itemList: (classNames, isSelectOneElement) => {
  return `
    <div class="${classNames.list} ${isSelectOneElement ? classNames.listSingle : classNames.listItems}">
      ${!isSelectOneElement ? '<div class="item-list-header">Selected:</div>' : ''}
    </div>
  `;
}

placeholder Template

Creates placeholder text element.

{ .api }
/**
 * @param classNames - CSS class configuration
 * @param value - Placeholder text
 * @returns {HTMLElement} - Placeholder element
 */
placeholder(classNames: ClassNames, value: string): HTMLElement

Custom Example:

placeholder: (classNames, value) => {
  return `
    <div class="${classNames.placeholder} custom-placeholder">
      <span class="placeholder-icon">🔍</span>
      <span class="placeholder-text">${value}</span>
    </div>
  `;
}

item Template

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): HTMLElement

Item 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>
  `;
}

choiceList Template

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): HTMLElement

choiceGroup Template

Creates 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): HTMLElement

Group 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>
  `;
}

choice Template

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): HTMLElement

Choice 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>
  `;
}

input Template

Creates the input/search element.

{ .api }
/**
 * @param classNames - CSS class configuration
 * @param placeholderValue - Placeholder text
 * @returns {HTMLElement} - Input element
 */
input(classNames: ClassNames, placeholderValue: string): HTMLElement

Custom Example:

input: (classNames, placeholderValue) => {
  return `
    <input type="search" 
           class="${classNames.input} custom-search-input"
           placeholder="${placeholderValue}"
           autocomplete="off"
           autocapitalize="off"
           spellcheck="false">
  `;
}

dropdown Template

Creates the dropdown container.

{ .api }
/**
 * @param classNames - CSS class configuration  
 * @returns {HTMLElement} - Dropdown container
 */
dropdown(classNames: ClassNames): HTMLElement

notice Template

Creates 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): HTMLElement

Custom 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>
  `;
}

option Template

Creates <option> elements for the original <select> (accessibility).

{ .api }
/**
 * @param data - Option data object
 * @returns {HTMLElement} - Option element
 */
option(data: Choice): HTMLElement

Advanced Template Patterns

Conditional Templates

callbackOnCreateTemplates: 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);
    }
  };
}

Rich Content Templates

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>
  `;
}

Dynamic Template Selection

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);
    }
  };
}

Template with Event Handlers

// 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;
    }
  };
}

Template Utilities

HTML Escaping

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>`;
}

Template Helpers

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>
  `;
}

Performance Considerations

Template Caching

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;
    }
  };
}

Minimal DOM Manipulation

// 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.