Группы в Tom Select позволяют логически объединять элементы выпадающего списка. Такая структура особенно полезна при работе с большими наборами данных: категориями товаров, странами и городами, языками программирования, тегами, ролями пользователей и другими сущностями.
Tom Select поддерживает группировку через свойство
optgroups. Каждая группа получает собственный
идентификатор, заголовок и набор связанных элементов.
Пример базовой структуры:
<select id="frameworks" multiple>
<option value="vue" data-group="frontend">Vue</option>
<option value="react" data-group="frontend">React</option>
<option value="laravel" data-group="backend">Laravel</option>
<option value="django" data-group="backend">Django</option>
</select>
new TomSelect('#frameworks', {
optgroupField: 'group',
optgroups: [
{value: 'frontend', label: 'Frontend'},
{value: 'backend', label: 'Backend'}
]
});
По умолчанию Tom Select отображает заголовки групп стандартным оформлением. Однако библиотека предоставляет широкие возможности для кастомизации внешнего вида.
Перед стилизацией важно понимать, какие элементы создаёт библиотека.
Tom Select формирует следующую структуру:
<div class="optgroup">
<div class="optgroup-header">
Frontend
</div>
<div data-value="vue" class="option">
Vue
</div>
<div data-value="react" class="option">
React
</div>
</div>
Основные классы:
| Класс | Назначение |
|---|---|
.optgroup |
Контейнер группы |
.optgroup-header |
Заголовок группы |
.option |
Элемент списка |
Эти классы используются как основные точки кастомизации.
Наиболее распространённая задача — визуально отделить заголовок группы от элементов списка.
.ts-dropdown .optgroup-header {
background: #f3f4f6;
color: #111827;
padding: 10px 14px;
font-weight: 600;
border-bottom: 1px solid #d1d5db;
}
Результат:
Для административных интерфейсов часто применяются заголовки в верхнем регистре.
.ts-dropdown .optgroup-header {
text-transform: uppercase;
letter-spacing: 1px;
font-size: 12px;
}
Такой стиль делает группы похожими на секции меню.
Без дополнительных отступов группы могут выглядеть перегруженно.
.ts-dropdown .optgroup {
padding-bottom: 12px;
}
.ts-dropdown .optgroup-header {
margin-bottom: 6px;
}
Это улучшает читаемость длинных списков.
Для визуального разделения удобно использовать
border.
.ts-dropdown .optgroup {
border-bottom: 1px solid #e5e7eb;
}
.ts-dropdown .optgroup:last-child {
border-bottom: none;
}
Такой подход особенно полезен при большом количестве категорий.
Иногда достаточно только пространства между блоками.
.ts-dropdown .optgroup {
margin-bottom: 18px;
}
Минималистичный интерфейс часто выглядит лучше без линий-разделителей.
Каждой группе можно назначить собственный класс через кастомный рендеринг.
new TomSelect('#frameworks', {
optgroupField: 'group',
render: {
optgroup_header(data, escape) {
return `
<div class="optgroup-header group-${data.value}">
${escape(data.label)}
</div>
`;
}
}
});
CSS:
.group-frontend {
background: #dbeafe;
color: #1e3a8a;
}
.group-backend {
background: #dcfce7;
color: #166534;
}
Теперь каждая категория имеет индивидуальное оформление.
Элементы также можно оформлять по принадлежности к группе.
render: {
option(data, escape) {
return `
<div class="option option-${data.group}">
${escape(data.text)}
</div>
`;
}
}
.option-frontend {
border-left: 3px solid #2563eb;
}
.option-backend {
border-left: 3px solid #16a34a;
}
Такой подход делает структуру списка более очевидной.
Tom Select позволяет полностью заменить HTML группы.
new TomSelect('#frameworks', {
render: {
optgroup_header(data, escape) {
return `
<div class="custom-group-header">
<span class="group-icon">?</span>
<span class="group-title">
${escape(data.label)}
</span>
</div>
`;
}
}
});
Можно добавлять:
Иногда требуется показать число элементов внутри группы.
new TomSelect('#frameworks', {
render: {
optgroup_header(data, escape) {
const count = data.$options.length;
return `
<div class="group-header">
<span>${escape(data.label)}</span>
<span class="group-count">${count}</span>
</div>
`;
}
}
});
CSS:
.group-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.group-count {
background: #2563eb;
color: white;
border-radius: 999px;
padding: 2px 8px;
font-size: 12px;
}
.ts-dropdown .optgroup:hover .optgroup-header {
background: #e0f2fe;
}
Такой эффект делает интерфейс более интерактивным.
.ts-dropdown .option.active {
background: #2563eb;
color: white;
}
При наличии групп важно, чтобы активный элемент визуально выделялся сильнее заголовков.
Для плотных интерфейсов применяются компактные стили.
.ts-dropdown .optgroup-header {
padding: 4px 8px;
font-size: 11px;
}
.ts-dropdown .option {
padding: 5px 8px;
}
Такой режим полезен:
Иногда оформление заголовков должно быть максимально нейтральным.
.ts-dropdown .optgroup-header {
background: transparent;
border: none;
color: #6b7280;
font-weight: 500;
}
Такой подход хорошо сочетается с современными минималистичными интерфейсами.
:root {
--group-bg: #f9fafb;
--group-color: #111827;
--group-border: #d1d5db;
}
.ts-dropdown .optgroup-header {
background: var(--group-bg);
color: var(--group-color);
border-bottom: 1px solid var(--group-border);
}
Преимущества:
.dark .ts-dropdown {
background: #111827;
border-color: #374151;
}
.dark .optgroup-header {
background: #1f2937;
color: #f3f4f6;
}
.dark .option {
color: #e5e7eb;
}
.dark .option.active {
background: #2563eb;
}
Тёмная тема требует отдельной настройки контраста:
.ts-dropdown .optgroup-header {
transition:
background 0.2s ease,
color 0.2s ease;
}
.ts-dropdown .optgroup:hover .optgroup-header {
transform: translateX(2px);
}
.ts-dropdown .optgroup-header {
transition: transform 0.15s ease;
}
Небольшие анимации делают интерфейс более живым без перегрузки.
.ts-dropdown .optgroup-header::before {
content: "▸";
margin-right: 8px;
color: #6b7280;
}
render: {
optgroup_header(data, escape) {
return `
<div class="group-header">
<svg class="group-icon" viewBox="0 0 24 24">
<path d="..."></path>
</svg>
<span>${escape(data.label)}</span>
</div>
`;
}
}
SVG позволяет:
optgroups: [
{
value: 'frontend',
label: 'Frontend',
description: 'Клиентская часть приложения'
}
]
render: {
optgroup_header(data, escape) {
return `
<div class="group-header">
<div class="title">
${escape(data.label)}
</div>
<div class="description">
${escape(data.description)}
</div>
</div>
`;
}
}
CSS:
.group-header .description {
font-size: 12px;
color: #6b7280;
margin-top: 2px;
}
При длинных списках полезно закреплять активную группу сверху.
.ts-dropdown .optgroup-header {
position: sticky;
top: 0;
z-index: 10;
background: white;
}
Такой подход улучшает навигацию по большим наборам данных.
.option[data-group="frontend"] {
background: #eff6ff;
}
.option[data-group="backend"] {
background: #f0fdf4;
}
Подход удобен, когда структура формируется динамически.
@media (max-width: 768px) {
.ts-dropdown .optgroup-header {
font-size: 13px;
padding: 8px 10px;
}
.ts-dropdown .option {
padding: 10px;
}
}
На сенсорных устройствах важно увеличивать область нажатия.
optgroups: [
{
value: 'legacy',
label: 'Устаревшие',
disabled: true
}
]
CSS:
.ts-dropdown .optgroup.disabled {
opacity: 0.5;
}
.ts-dropdown .optgroup.disabled .optgroup-header {
text-decoration: line-through;
}
Это помогает визуально отделять недоступные секции.
.group-header {
display: flex;
justify-content: space-between;
align-items: center;
}
Flexbox особенно полезен при наличии:
.group-header {
display: grid;
grid-template-columns: 1fr auto;
gap: 10px;
}
Grid удобен для многокомпонентных шаблонов.
Группы должны визуально отличаться от обычных элементов списка.
Рекомендуется:
Пример:
.optgroup-header {
font-size: 13px;
font-weight: 700;
}
.option {
font-size: 14px;
font-weight: 400;
}
Большое количество сложных эффектов может ухудшать производительность выпадающего списка.
Особенно затратны:
box-shadow;backdrop-filter.Для крупных списков рекомендуется:
render().Tom Select хорошо сочетается с:
Пример с Tailwind-подобным стилем:
.optgroup-header {
background: #f9fafb;
color: #374151;
font-weight: 600;
padding: 8px 12px;
border-bottom: 1px solid #e5e7eb;
}
.option.active {
background: #3b82f6;
color: white;
}
new TomSelect('#frameworks', {
optgroupField: 'group',
optgroups: [
{
value: 'frontend',
label: 'Frontend'
},
{
value: 'backend',
label: 'Backend'
}
],
render: {
optgroup_header(data, escape) {
return `
<div class="custom-group-header group-${data.value}">
<span class="title">
${escape(data.label)}
</span>
<span class="badge">
${data.$options.length}
</span>
</div>
`;
},
option(data, escape) {
return `
<div class="custom-option option-${data.group}">
${escape(data.text)}
</div>
`;
}
}
});
.custom-group-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 14px;
font-weight: 700;
border-radius: 6px;
}
.group-frontend {
background: #dbeafe;
color: #1e40af;
}
.group-backend {
background: #dcfce7;
color: #166534;
}
.badge {
background: rgba(255,255,255,0.7);
padding: 2px 8px;
border-radius: 999px;
font-size: 12px;
}
.custom-option {
padding: 10px 14px;
}
.custom-option:hover {
background: #f3f4f6;
}