브라우저처럼 스크롤되는 React 스크롤 탭

탭 스트립은 제품이 탭 6개를 넘어서는 순간 더 이상 맞지 않게 됩니다. 해결책은 폰트를 줄이는 것이 아니라 스크롤되는 스트립입니다: 오버플로는 브라우저가 처리하고, 탭을 클릭하면 중앙으로 정렬되며, 화살표는 갈 곳이 있을 때만 나타납니다.

가장자리 근처의 탭을 클릭해보세요 — 스스로 중앙으로 스크롤됩니다.

중요한 단 하나의 동작: 선택 시 중앙 정렬

스크롤 탭 스트립의 성패는 가장자리에 있는 탭을 클릭했을 때 일어나는 일에 달려 있습니다: 양옆의 이웃 탭이 드러나도록 가운데로 부드럽게 이동해야 합니다. 여기서는 호출 하나로 끝납니다 — scrollToItem(el, 'smooth', 'center')center-on-click 예제에 연결되어 있습니다. 마운트 시 활성 탭을 복원하는 것도 같은 호출에 'auto'를 사용하며, 위치 저장 및 복원에서 보여줍니다.

화살표는 같은 가시성 데이터에서 나옵니다: useLeftArrowVisible은 첫 번째 탭이 화면 밖에 있을 때만 false이므로, 왼쪽 화살표는 정확히 필요할 때 렌더링됩니다. 별도의 측정 코드도, 직접 만든 리사이즈 옵저버도 필요 없습니다.

MUI 스크롤 탭이 부족해지고 있다면

Material UI의 variant="scrollable" 탭은 Material 디자인 시스템 안에서는 올바른 답입니다 — 여러분의 "탭"이 더 이상 탭이 아니게 되기 전까지는요. MUI는 스트립을 Tabs 시맨틱에 용접해 놓았습니다: value/onChange 쌍, 탭 패널, 그리고 MUI가 모바일에서 기본으로 숨기는 스크롤 버튼까지. 행에 칩, 카드, 아바타 또는 혼합 콘텐츠가 들어가거나 드래그 스크롤이 필요하거나 어떤 항목이 보이는지 알아야 하는 순간, 여러분은 컴포넌트를 사용하는 게 아니라 컴포넌트와 싸우게 됩니다.

이 라이브러리는 그 아래 계층입니다: 가시성 추적이 있는 스크롤 행이며, "탭"이 무엇이어야 하는지에 대한 의견이 없습니다. 여러분의 탭은 itemId를 가진 모든 컴포넌트일 수 있습니다 — Tailwind, MUI 자체의 styled, 또는 순수 CSS로 스타일링하세요. 선택 상태는 위 데모가 하나의 useState에 보관하는 것처럼 온전히 여러분의 몫입니다.

접근성은 대부분 공짜입니다 — 두 가지 빈틈만 주의하세요

스트립이 네이티브 스크롤 컨테이너이기 때문에 키보드 포커스, 스크린 리더 읽기 순서, RTL은 모두 플랫폼에서 제공됩니다 — 탭 사이로 포커스를 이동하면 코드 한 줄 없이도 해당 탭이 화면에 스크롤되어 들어오고, RTL도 별도 설정이 필요 없습니다. 다른 탭 UI와 마찬가지로 두 가지는 여전히 여러분의 몫입니다: ARIA 패턴을 직접 선택하고(실제 패널이 전환된다면 role="tablist", "탭"이 내비게이션이라면 aria-current), 드래그 스크롤 레시피의 클릭 억제 로직을 유지해 드래그를 놓았을 때 탭이 활성화되지 않도록 해야 합니다.

패턴, 최소 구성

탭은 itemId를 가진 평범한 버튼이며, 선택하면 중앙으로 정렬됩니다. 이것이 전부입니다 — 위 데모는 여기에 스타일링과 드래그를 더한 것입니다.

ScrollTabs.tsx
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/에 수정 가능한 컴포넌트로 설치됩니다:

shadcn
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/scroll-tabs.json

관련 예제

예제 전체 21개