Веб

Как сделать выпадающий список с горизонтальной прокруткой

Нативный <select> не выходит за пределы контейнера с overflow:hidden. Решение: заменить на кастомный <div>-компонент с горизонтальной прокруткой, управляемый JS.

Как реализовать выпадающий список (select) внутри контейнера, у которого скрыты элементы и должна быть горизонтальная прокрутка? Я попытался использовать overflow: auto, но выпадающий список не виден. Ссылка на пример: https://codepen.io/Valery-Serebryakov/pen/pvyYoEm

Выпадающий список, который выходит за пределы контейнера с overflow:hidden, не виден, потому что браузер рендерит меню <select> как отдельный элемент, который обрезается родительским слоем.
Чтобы реализовать горизонтальную прокрутку и при этом показать выпадающий список, нужно заменить нативный <select> на кастомный компонент, который выводится вне ограничивающего слоя и управляется JavaScript‑ом. Ниже приведён пошаговый план решения.

Содержание

Почему overflow:hidden скрывает выпадающий список?

Нативный элемент <select> является замещённым элементом (replaced element).
При открытии меню браузер рисует его вне DOM‑дерева, но всё равно учитывает свойства overflow родительского контейнера.

Из‑за того, что контейнер имеет overflow:hidden, часть выпадающего меню, которая выходит за пределы элемента, обрезается. Это стандартное поведение, описанное в спецификации CSS overflow – https://developer.mozilla.org/en-US/docs/Web/CSS/overflow.

Поэтому не получится показать нативный <select> внутри контейнера с overflow:hidden. Для решения нужен кастомный компонент, который можно разместить в любом месте страницы (например, в <body>), но при этом позиционироваться относительно оригинального <div>‑контроля.

Как создать кастомный выпадающий список с горизонтальной прокруткой

  1. Базовый 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>
    
  2. Стили для контейнера

    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; }
    
  3. Горизонтальная прокрутка
    Если элементы списка шире контейнера, они автоматически становятся «скользящими» по горизонтали, благодаря overflow-x:auto.

    css
    .dropdown-item {
      white-space: nowrap;   /* предотвращаем перенос текста */
      padding: 8px 12px;
    }
    
  4. JavaScript‑логика

    js
    const 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 обеспечивает горизонтальную прокрутку элементов, если они шире.

Полный пример кода

html
<!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 родительского контейнера.

Авторы
Проверено модерацией
Модерация