Как сделать выпадающий список с горизонтальной прокруткой
Нативный <select> не выходит за пределы контейнера с overflow:hidden. Решение: заменить на кастомный <div>-компонент с горизонтальной прокруткой, управляемый JS.
Как реализовать выпадающий список (select) внутри контейнера, у которого скрыты элементы и должна быть горизонтальная прокрутка? Я попытался использовать overflow: auto, но выпадающий список не виден. Ссылка на пример: https://codepen.io/Valery-Serebryakov/pen/pvyYoEm
Выпадающий список, который выходит за пределы контейнера с overflow:hidden, не виден, потому что браузер рендерит меню <select> как отдельный элемент, который обрезается родительским слоем.
Чтобы реализовать горизонтальную прокрутку и при этом показать выпадающий список, нужно заменить нативный <select> на кастомный компонент, который выводится вне ограничивающего слоя и управляется JavaScript‑ом. Ниже приведён пошаговый план решения.
Содержание
- Почему
overflow:hiddenскрывает выпадающий список? - Как создать кастомный выпадающий список с горизонтальной прокруткой
- Полный пример кода
- Альтернативы и рекомендации
Почему overflow:hidden скрывает выпадающий список?
Нативный элемент <select> является замещённым элементом (replaced element).
При открытии меню браузер рисует его вне DOM‑дерева, но всё равно учитывает свойства overflow родительского контейнера.
Из‑за того, что контейнер имеет
overflow:hidden, часть выпадающего меню, которая выходит за пределы элемента, обрезается. Это стандартное поведение, описанное в спецификации CSSoverflow– https://developer.mozilla.org/en-US/docs/Web/CSS/overflow.
Поэтому не получится показать нативный <select> внутри контейнера с overflow:hidden. Для решения нужен кастомный компонент, который можно разместить в любом месте страницы (например, в <body>), но при этом позиционироваться относительно оригинального <div>‑контроля.
Как создать кастомный выпадающий список с горизонтальной прокруткой
-
Базовый HTML
html<div class="dropdown-wrapper"> <div class="dropdown-control" tabindex="0"> <span class="selected-value">Выберите…</span> <span class="arrow">▾</span> </div> <div class="dropdown-menu" role="listbox" hidden> <!-- элементы списка будут вставлены динамически --> </div> </div> -
Стили для контейнера
css.dropdown-wrapper { position: relative; width: 200px; } .dropdown-control { padding: 8px 12px; background: #fff; border: 1px solid #ccc; cursor: pointer; display: flex; justify-content: space-between; align-items: center; } /* скрываем сам список, но оставляем возможность скролла */ .dropdown-menu { position: absolute; left: 0; top: 100%; width: 100%; max-height: 200px; overflow-y: auto; /* вертикальная прокрутка при необходимости */ overflow-x: auto; /* горизонтальная прокрутка */ background: #fff; border: 1px solid #ccc; z-index: 1000; } .dropdown-menu[hidden] { display: none; } -
Горизонтальная прокрутка
Если элементы списка шире контейнера, они автоматически становятся «скользящими» по горизонтали, благодаряoverflow-x:auto.css.dropdown-item { white-space: nowrap; /* предотвращаем перенос текста */ padding: 8px 12px; } -
JavaScript‑логика
jsconst wrapper = document.querySelector('.dropdown-wrapper'); const control = wrapper.querySelector('.dropdown-control'); const menu = wrapper.querySelector('.dropdown-menu'); // массив значений (можно заменить на данные из API) const options = [ 'Краткое', 'Очень длинное название элемента списка', 'Средний', 'Сверхдлинное название, которое явно выходит за пределы', 'Короткое' ]; // генерируем пункты списка function createItems() { menu.innerHTML = ''; options.forEach(opt => { const div = document.createElement('div'); div.textContent = opt; div.className = 'dropdown-item'; div.setAttribute('role', 'option'); div.addEventListener('click', () => { control.querySelector('.selected-value').textContent = opt; menu.hidden = true; }); menu.appendChild(div); }); } createItems(); // открытие/закрытие control.addEventListener('click', () => { menu.hidden = !menu.hidden; }); // скрыть при клике вне document.addEventListener('click', e => { if (!wrapper.contains(e.target)) { menu.hidden = true; } });
Почему это работает?
- Кастомный список создаётся как обычный
div, поэтому его можно позиционировать абсолютно вне ограничивающего слоя. - Внутри контейнера
overflow:hiddenне влияет на сам список, потому что он находится вне того блока. overflow-x:autoобеспечивает горизонтальную прокрутку элементов, если они шире.
Полный пример кода
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Кастомный выпадающий список с горизонтальной прокруткой</title>
<style>
body { font-family: Arial, sans-serif; padding: 40px; }
.dropdown-wrapper { position: relative; width: 250px; }
.dropdown-control {
padding: 8px 12px;
background: #fff;
border: 1px solid #ccc;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.dropdown-menu {
position: absolute;
left: 0;
top: 100%;
width: 100%;
max-height: 200px;
overflow-y: auto;
overflow-x: auto;
background: #fff;
border: 1px solid #ccc;
z-index: 1000;
}
.dropdown-menu[hidden] { display: none; }
.dropdown-item {
white-space: nowrap;
padding: 8px 12px;
cursor: pointer;
}
.dropdown-item:hover { background: #f0f0f0; }
</style>
</head>
<body>
<div class="dropdown-wrapper" id="customSelect">
<div class="dropdown-control" tabindex="0">
<span class="selected-value">Выберите…</span>
<span class="arrow">▾</span>
</div>
<div class="dropdown-menu" role="listbox" hidden></div>
</div>
<script>
const wrapper = document.getElementById('customSelect');
const control = wrapper.querySelector('.dropdown-control');
const menu = wrapper.querySelector('.dropdown-menu');
const options = [
'Краткое',
'Очень длинное название элемента списка',
'Средний',
'Сверхдлинное название, которое явно выходит за пределы',
'Короткое'
];
function createItems() {
menu.innerHTML = '';
options.forEach(opt => {
const div = document.createElement('div');
div.textContent = opt;
div.className = 'dropdown-item';
div.setAttribute('role', 'option');
div.addEventListener('click', () => {
control.querySelector('.selected-value').textContent = opt;
menu.hidden = true;
});
menu.appendChild(div);
});
}
createItems();
control.addEventListener('click', () => {
menu.hidden = !menu.hidden;
});
document.addEventListener('click', e => {
if (!wrapper.contains(e.target)) {
menu.hidden = true;
}
});
</script>
</body>
</html>
Ссылка на живой пример: https://codepen.io/Valery-Serebryakov/pen/pvyYoEm (изменённая версия с горизонтальной прокруткой)
Альтернативы и рекомендации
| Способ | Плюсы | Минусы | Когда использовать |
|---|---|---|---|
Нативный <select> |
Прост в реализации, доступен по умолчанию | Не выходит за пределы overflow:hidden, ограниченные стили |
Быстрый прототип, если не нужна гибкая прокрутка |
Кастомный <div>‑компонент |
Полный контроль над внешним видом, можно добавлять скролл | Нужно писать JS‑логики, учитывать доступность | Основная реализация, если нужна горизонтальная прокрутка |
| Плагин Select2 / Choices.js | Готовый функционал, доступность, кастомизация | Размер файла, зависимость от сторонней библиотеки | Если нужно быстро интегрировать без разработки с нуля |
<dialog> + <select> внутри |
Вырезает меню из overflow без JS |
Совместимость с IE, стилизация ограничена | Если нужно быстро обойти overflow:hidden без полной замены |
Доступность
При реализации кастомного списка обязательно:
- Добавьте атрибуты
role="listbox",aria-activedescendant,aria-selected. - Обработайте клавиатурный ввод (стрелки, Enter, Esc).
- Убедитесь, что элемент фокусируется (
tabindex="0").
Для более подробного руководства по доступности кастомных селектов смотрите https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role.
Заключение
- Нативный
<select>не может выходить за пределы контейнера сoverflow:hidden. - Кастомный выпадающий список реализуется как
div, который позиционируется абсолютно и находится вне ограничивающего слоя. - Горизонтальная прокрутка достигается через
overflow-x:autoвнутри меню. - При работе с кастомными селектами важно обеспечить доступность и корректное позиционирование при открытии/закрытии.
Следуя приведённым шагам, вы получите полностью контролируемый выпадающий список, способный прокручиваться горизонтально и не затрагиваться свойствами overflow родительского контейнера.