---
title: "React 可滚动标签栏——无需 Material UI"
description: "使用原生滚动在 React 中实现可滚动标签栏：激活的标签会自动居中，箭头仅在需要时出现，标签内容形式自由。附带实时演示和源码。"
canonical: "https://react-horizontal-scrolling-menu.dev/zh-cn/scrollable-tabs"
image: "https://react-horizontal-scrolling-menu.dev/og.png"
---

# 像浏览器一样滚动的 React 可滚动标签栏

一旦你的产品的标签数量超过六个，标签栏就会装不下。解决办法不是缩小字号——而是让整条标签栏可以滚动：溢出交给浏览器处理，点击某个标签会使其居中，箭头只在还有地方可去时才出现。

Overview

Getting started

Visibility

Arrows

Drag to scroll

RTL

apiRef

Helpers

TypeScript

Testing

Changelog

FAQ

点击靠近边缘的标签——它会自动滚动到中间位置。

[在 Storybook 中实时编辑此示例](https://asmyshlyaev177.github.io/react-horizontal-scrolling-menu/?path=/story/examples-centeronclick--center-on-click)

## 唯一重要的行为：选中即居中

一个可滚动标签栏是否好用，取决于点击边缘标签时会发生什么：它应当平滑滑动到中间，将两侧的相邻标签露出来。这里只需一次调用——`scrollToItem(el, 'smooth', 'center')`——已经在 [选中即居中示例](https://react-horizontal-scrolling-menu.dev/zh-cn/examples/center-on-click.md) 中接好。挂载时恢复激活标签用的是同一个调用，只是把参数换成 `'auto'`，见 [保存与恢复位置](https://react-horizontal-scrolling-menu.dev/zh-cn/examples/save-restore-position.md)。

箭头来自同一份可见性数据：只有当第一个标签移出屏幕时，`useLeftArrowVisible` 才会为 false，因此左箭头恰好只在有用时才渲染。不需要自己写测量代码，也不需要自己的 resize observer。

## 如果你正在超出 MUI 可滚动标签栏的能力范围

在 Material 设计体系内，Material UI 的 `variant="scrollable"` 标签是正确答案——直到你的“标签”不再是标签为止。MUI 把这条栏牢牢焊死在 Tabs 语义上：一对 `value`/`onChange`、标签面板，以及 MUI 在移动端默认隐藏的滚动按钮。一旦你的这一行需要容纳纸片、卡片、头像或混合内容，或者需要拖拽滚动，或者需要知道哪些项目可见，你就是在和这个组件较劲，而不是在使用它。

本库处于更底层：一个带可见性跟踪的可滚动行，对“标签”是什么不做任何假设。你的标签可以是任何带有 `itemId` 的组件——用 Tailwind、MUI 自己的 `styled`，或纯 CSS 来设置样式都可以。选中状态始终由你掌控，就像上方演示那样，只用一个 `useState` 保存。

## 无障碍访问基本是免费的——但要留意两处空白

因为这条栏本身就是一个原生滚动容器，键盘焦点、屏幕阅读器的阅读顺序以及 RTL 都由平台自动处理——焦点在标签间移动时会自动滚动进入可视区域，无需任何代码，[RTL](https://react-horizontal-scrolling-menu.dev/zh-cn/examples/rtl.md) 也无需额外配置。有两件事仍需你自己处理，这和其他任何标签 UI 一样：选择合适的 ARIA 模式（如果确实会切换面板，用 `role="tablist"`；如果这些“标签”实际是导航，用 `aria-current`），并沿用 [拖拽滚动](https://react-horizontal-scrolling-menu.dev/zh-cn/examples/mouse-drag.md) 方案中的点击抑制逻辑，以确保松开拖拽时不会误触发某个标签。

## 最简模式

标签就是带有 `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](https://react-horizontal-scrolling-menu.dev/r/scroll-tabs.json) 注册表条目以数据驱动的方式提供这一模式——传入 `tabs`、`value`、`onValueChange` 即可——作为可编辑组件安装进你的 `components/ui/`：

shadcn

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

## 相关示例

-   [让点击的项目居中scrollToItem 配 inline: center——可滚动标签页模式。](https://react-horizontal-scrolling-menu.dev/zh-cn/examples/center-on-click.md)
-   [保存与恢复滚动位置在卸载与页面重载之间保持滚动偏移。](https://react-horizontal-scrolling-menu.dev/zh-cn/examples/save-restore-position.md)
-   [用鼠标拖拽滚动鼠标拖拽的同时不破坏项目点击。](https://react-horizontal-scrolling-menu.dev/zh-cn/examples/mouse-drag.md)

[全部 21 个示例](https://react-horizontal-scrolling-menu.dev/zh-cn/examples.md)

---

More examples: <https://react-horizontal-scrolling-menu.dev/examples.md>
Library summary for LLMs: <https://react-horizontal-scrolling-menu.dev/llms.txt>
