Прокручиваемые вкладки React, которые прокручиваются как браузер
Полоса вкладок перестаёт помещаться, как только в продукте становится больше шести вкладок. Решение — не уменьшать шрифт, а сделать полосу прокручиваемой: переполнение обрабатывает браузер, клик по вкладке центрирует её, а стрелки появляются только тогда, когда есть куда прокручивать.
Кликните по вкладке у края — она сама прокрутится к центру.
Редактировать этот пример вживую в Storybook
Единственное важное поведение: центрирование при выборе
Судьба прокручиваемой полосы вкладок решается тем, что происходит при клике по вкладке у края: она должна плавно переместиться к середине, открывая соседей с обеих сторон. Здесь это один вызов — scrollToItem(el, 'smooth', 'center') — реализованный в примере center-on-click. Восстановление активной вкладки при монтировании — тот же вызов с 'auto', показанный в примере сохранения и восстановления позиции.
Стрелки берут те же данные о видимости: useLeftArrowVisible равно false, только пока первая вкладка вне экрана, поэтому левая стрелка рендерится именно тогда, когда она полезна. Никакого кода измерений и собственных resize-обсерверов не нужно.
Если вам стало тесно в прокручиваемых вкладках MUI
Вкладки Material UI с variant="scrollable" — правильный выбор внутри дизайн-системы Material, пока ваши «вкладки» остаются вкладками. MUI жёстко привязывает полосу к семантике Tabs: пара value/onChange, панели вкладок и кнопки прокрутки, которые MUI по умолчанию скрывает на мобильных устройствах. Как только в ряду появляются чипы, карточки, аватары или смешанный контент, или требуется перетаскивание для прокрутки, или нужно знать, какие элементы видны, — вы уже боретесь с компонентом, а не используете его.
Эта библиотека — слой уровнем ниже: прокручиваемый ряд с отслеживанием видимости, без каких-либо представлений о том, что такое «вкладка». Ваша вкладка — это любой компонент с itemId: стилизуйте его через Tailwind, собственный styled из MUI или обычный CSS. Состояние выбора остаётся вашим — точно так же демо выше хранит его в одном useState.
Доступность почти бесплатна — учтите два пробела
Поскольку полоса — это нативный контейнер прокрутки, фокус клавиатуры, порядок чтения для скринридеров и RTL обеспечивает сама платформа: перемещение фокуса по вкладкам без единой строки кода прокручивает их в область видимости, а RTL не требует никакой настройки. Как и в любом UI с вкладками, на вас остаются две вещи: выбрать паттерн ARIA (role="tablist", если переключаются настоящие панели, aria-current, если «вкладки» — это навигация), и сохранить подавление кликов из рецепта drag-to-scroll, чтобы отпускание перетаскивания никогда не активировало вкладку.
Паттерн в минимальном виде
Вкладки — это обычные кнопки с itemId; выбор одной центрирует её. В этом вся идея — демо выше добавляет стилизацию и перетаскивание.
function ScrollTabs({ tabs }: { tabs: string[] }) {
const [active, setActive] = React.useState(tabs[0]);
return (
<ScrollMenu LeftArrow={LeftArrow} RightArrow={RightArrow}>
{tabs.map((tab) => (
<Tab itemId={tab} key={tab} label={tab}
active={tab === active} onSelect={() => setActive(tab)} />
))}
</ScrollMenu>
);
}
function Tab({ itemId, label, active, onSelect }: TabProps) {
const api = React.useContext<publicApiType>(VisibilityContext);
return (
<button
aria-current={active}
onClick={(ev) => {
onSelect();
// The behavior that makes tabs feel native: selecting an
// edge tab glides it to the center of the strip.
api.scrollToItem(ev.currentTarget, 'smooth', 'center');
}}
>
{label}
</button>
);
}Или установите как компонент shadcn
Элемент реестра scroll-tabs реализует этот паттерн на основе данных — передайте tabs, value, onValueChange — в виде редактируемого компонента в вашей components/ui/:
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/scroll-tabs.json