В интерфейсах, построенных на Choices.js, визуальная индикация ошибок является частью общей стратегии управления состоянием поля выбора и синхронизации данных формы с пользовательским вводом. Библиотека не навязывает единственный способ отображения ошибок, однако предоставляет механизмы, через которые можно интегрировать внешнюю или внутреннюю валидацию и отражать её результаты в UI.
Основная сложность при работе с кастомными select-компонентами
заключается в том, что нативные механизмы браузера
(:invalid, :valid) перестают применяться
напрямую. DOM-элемент скрывается, а управление вводом переходит к
JavaScript-слою, который полностью отвечает за состояние визуального
компонента.
В Choices.js поле выбора можно рассматривать как совокупность трёх уровней состояния:
Именно третий уровень требует отдельной логики, поскольку библиотека не выполняет полноценную валидацию, а лишь предоставляет структуру для отображения результатов.
Наиболее распространённый вариант — использование стандартной HTML-валидации или сторонних валидаторов (например, на уровне бизнес-логики), с последующим обновлением UI компонента.
Пример базовой интеграции:
const element = document.querySelector('#select');
const choices = new Choices(element, {
shouldSort: false,
removeItemButton: true
});
const form = document.querySelector('form');
form.addEventListener('submit', (e) => {
const value = choices.getValue(true);
if (!value || value.length === 0) {
e.preventDefault();
showError(element, 'Необходимо выбрать хотя бы один элемент');
}
});
Функция визуального отображения ошибки обычно работает через модификацию классов и вставку текстового блока:
function showError(el, message) {
const wrapper = el.closest('.choices');
wrapper.classList.add('is-invalid');
let error = wrapper.querySelector('.choices-error');
if (!error) {
error = document.createElement('div');
error.className = 'choices-error';
wrapper.appendChild(error);
}
error.textContent = message;
}
Choices.js активно опирается на классы контейнера
.choices, что позволяет расширять визуальные состояния без
вмешательства в внутреннюю структуру.
Типовые состояния:
.is-focused — поле в фокусе.is-open — список раскрыт.is-disabled — отключённое состояниеПример стилизации ошибки:
.choices.is-invalid .choices__inner {
border-color: #d9534f;
box-shadow: 0 0 0 1px rgba(217, 83, 79, 0.25);
}
.choices-error {
color: #d9534f;
font-size: 12px;
margin-top: 4px;
}
Такой подход отделяет визуальную логику от бизнес-логики и позволяет гибко управлять внешним видом без изменения JavaScript-кода библиотеки.
Важный аспект — синхронизация ошибки с изменением значения. Ошибка должна автоматически исчезать при корректном вводе или выборе.
element.addEventListener('change', () => {
const wrapper = element.closest('.choices');
if (wrapper.classList.contains('is-invalid')) {
wrapper.classList.remove('is-invalid');
const error = wrapper.querySelector('.choices-error');
if (error) error.remove();
}
});
Для множественного выбора логика обычно расширяется проверкой длины массива значений.
Choices.js предоставляет набор событий, которые можно использовать для отслеживания изменений состояния. Наиболее полезные для индикации ошибок:
addItemremoveItemchangechoiceПример реактивной валидации:
choices.passedElement.element.addEventListener('addItem', () => {
clearError(element);
});
choices.passedElement.element.addEventListener('removeItem', () => {
const values = choices.getValue(true);
if (values.length === 0) {
showError(element, 'Выбор не может быть пустым');
}
});
В ряде сценариев требуется проверка значений на сервере: уникальность, доступность, ограничения по правам доступа. В таких случаях визуальная индикация ошибки становится результатом асинхронного запроса.
async function validateSelection(value) {
const response = await fetch('/validate', {
method: 'POST',
body: JSON.stringify({ value }),
headers: { 'Content-Type': 'application/json' }
});
return response.json();
}
Использование в контексте Choices:
element.addEventListener('change', async () => {
const value = choices.getValue(true);
const result = await validateSelection(value);
if (!result.valid) {
showError(element, result.message);
}
});
При сложной форме возможны ситуации, когда одно поле должно отображать несколько типов ошибок одновременно:
В таких случаях применяется приоритетизация сообщений:
function resolveError(errors) {
if (errors.required) return errors.required;
if (errors.limit) return errors.limit;
if (errors.server) return errors.server;
return null;
}
Отображается только одно активное сообщение, чтобы избежать перегрузки интерфейса.
Визуальная индикация должна сопровождаться семантическими изменениями
для экранных читалок. Choices.js позволяет модифицировать
aria-invalid и aria-describedby.
function setAriaError(el, message) {
el.setAttribute('aria-invalid', 'true');
const id = `${el.id}-error`;
let error = document.getElementById(id);
if (!error) {
error = document.createElement('div');
error.id = id;
error.className = 'choices-error';
el.parentNode.appendChild(error);
}
error.textContent = message;
el.setAttribute('aria-describedby', id);
}
При очистке ошибки атрибуты также должны возвращаться в исходное состояние.
Особое внимание требуется при сочетании ошибок и отключённого
состояния. Поле, находящееся в состоянии disabled, не
должно отображать ошибку как активную, даже если валидатор возвращает
негативный результат.
if (element.disabled) {
clearError(element);
return;
}
Это предотвращает визуальные противоречия в интерфейсе.
Choices.js поддерживает переопределение шаблонов, что позволяет встроить индикацию ошибки непосредственно в структуру компонента. Такой подход уменьшает зависимость от внешнего DOM-манипулирования.
Пример расширения контейнера:
const choices = new Choices(element, {
callbackOnCreateTemplates: function (template) {
return {
containerOuter: (classNames, data, hasFocus) => {
const div = template(`
<div class="${classNames.containerOuter}">
<div class="choices__inner"></div>
<div class="choices-error" data-error></div>
</div>
`);
return div;
}
};
}
});
В многоэлементных формах логика ошибок часто централизуется. Choices.js выступает только как слой отображения, а источник истины находится в объекте состояния формы.
const formState = {
country: null,
tags: []
};
function validateForm(state) {
return {
country: state.country ? null : 'Страна обязательна',
tags: state.tags.length > 3 ? 'Максимум 3 элемента' : null
};
}
После пересчёта состояния ошибки распределяются по соответствующим экземплярам Choices.
При использовании динамической загрузки (setChoices,
clearChoices) важно учитывать сброс состояния ошибок.
Изменение набора данных может автоматически обнулять валидность
выбора.
choices.setChoices([], 'value', 'label', true);
clearError(element);
Это предотвращает ситуации, когда ошибка остаётся после изменения контекста выбора.
Поведение визуальной индикации можно описать как цикл:
Такой цикл обеспечивает предсказуемость интерфейса и согласованность отображения состояния в сложных формах.