选项卡
轻松创建可访问的、完全可定制的选项卡界面,并提供强大的焦点管理和键盘导航支持。
要开始使用,请通过 npm 安装无头 UI
npm install @headlessui/react
选项卡使用 TabGroup
、TabList
、Tab
、TabPanels
和 TabPanel
组件构建。默认情况下,第一个选项卡被选中,单击任何选项卡或使用键盘选择它将激活相应的面板。
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
function Example() {
return (
<TabGroup>
<TabList>
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
<Tab>Tab 3</Tab>
</TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
<TabPanel>Content 3</TabPanel>
</TabPanels>
</TabGroup>
)
}
无头 UI 会跟踪每个组件的大量状态,例如当前选中的选项卡、弹出框是打开还是关闭,或者菜单中哪个项目当前通过键盘获得焦点。
但由于这些组件是无头的,并且在开箱即用时完全没有样式,因此您无法在 UI 中看到这些信息,直到您自己为每个状态提供所需的样式。
为无头 UI 组件的不同状态设置样式最简单的方法是使用每个组件公开的 data-*
属性。
例如,Tab
组件公开了一个 data-selected
属性,它告诉你该选项卡当前是否被选中,以及一个 data-hover
属性,它告诉你该选项卡当前是否被鼠标悬停。
<!-- Rendered `TabGroup` -->
<div>
<div>
<button>Tab 1</button>
<button data-selected>Tab 2</button>
<button data-hover>Tab 3</button>
</div>
<div>
<div>Content 1</div>
<div data-selected>Content 2</div>
<div>Content 3</div>
</div>
</div>
使用 CSS 属性选择器 根据这些数据属性的存在条件地应用样式。如果您使用的是 Tailwind CSS,数据属性修饰符 使此操作变得容易
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
function Example() {
return (
<TabGroup>
<TabList>
<Tab className="data-[selected]:bg-blue-500 data-[selected]:text-white data-[hover]:underline">Tab 1</Tab> <Tab className="data-[selected]:bg-blue-500 data-[selected]:text-white data-[hover]:underline">Tab 2</Tab> <Tab className="data-[selected]:bg-blue-500 data-[selected]:text-white data-[hover]:underline">Tab 3</Tab> </TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
<TabPanel>Content 3</TabPanel>
</TabPanels>
</TabGroup>
)
}
请参阅 组件 API 以获取所有可用数据属性的列表。
每个组件还通过 渲染道具 公开有关其当前状态的信息,您可以使用这些信息来条件地应用不同的样式或呈现不同的内容。
例如,Tab
组件公开了一个 selected
状态,它告诉你该选项卡当前是否被选中,以及一个 hover
状态,它告诉你该选项卡当前是否被鼠标悬停。
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
import clsx from 'clsx'
import { Fragment } from 'react'
function Example() {
return (
<TabGroup>
<TabList>
<Tab as={Fragment}>
{({ hover, selected }) => ( <button className={clsx(hover && 'underline', selected && 'bg-blue-500 text-white')}>Tab 1</button> )} </Tab>
<Tab as={Fragment}>
{({ hover, selected }) => ( <button className={clsx(hover && 'underline', selected && 'bg-blue-500 text-white')}>Tab 2</button> )} </Tab>
<Tab as={Fragment}>
{({ hover, selected }) => ( <button className={clsx(hover && 'underline', selected && 'bg-blue-500 text-white')}>Tab 3</button> )} </Tab>
</TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
<TabPanel>Content 3</TabPanel>
</TabPanels>
</TabGroup>
)
}
请参阅 组件 API 以获取所有可用渲染道具的列表。
使用 disabled
道具禁用 Tab
并阻止其被选中
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
function Example() {
return (
<TabGroup>
<TabList>
<Tab>Tab 1</Tab>
<Tab disabled className="disabled:opacity-50"> Tab 2
</Tab>
<Tab>Tab 3</Tab>
</TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
<TabPanel>Content 3</TabPanel>
</TabPanels>
</TabGroup>
)
}
如果您已将 TabList
设置为垂直显示,请使用 vertical
道具启用使用向上和向下箭头键而不是向左和向右导航,并更新 aria-orientation
属性以供辅助技术使用。
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
function Example() {
return (
<TabGroup vertical> <TabList className="flex flex-col">
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
<Tab>Tab 3</Tab>
</TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
<TabPanel>Content 3</TabPanel>
</TabPanels>
</TabGroup>
)
}
默认情况下,选项卡会随着用户使用箭头键在选项卡之间导航而自动选中。
如果您希望在用户按下 Enter
或 Space
之前不更改当前选项卡,请在 TabGroup
组件上使用 manual
道具。如果选择选项卡会执行昂贵的操作并且您不想不必要地运行它,这会很有帮助。
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
function Example() {
return (
<TabGroup manual> <TabList>
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
<Tab>Tab 3</Tab>
</TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
<TabPanel>Content 3</TabPanel>
</TabPanels>
</TabGroup>
)
}
manual
道具不会影响鼠标交互 - 选项卡仍会在被点击后立即选中。
要更改默认情况下选中的选项卡,请在 TabGroup
组件上使用 defaultIndex={number}
道具。
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
function Example() {
return (
<TabGroup defaultIndex={1}> <TabList>
<Tab>Tab 1</Tab>
{/* Selects this tab by default */} <Tab>Tab 2</Tab>
<Tab>Tab 3</Tab>
</TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
{/* Displays this panel by default */} <TabPanel>Content 2</TabPanel>
<TabPanel>Content 3</TabPanel>
</TabPanels>
</TabGroup>
)
}
如果您恰好提供了一个超出范围的索引,那么在初始渲染时将选中最后一个未禁用的选项卡。(例如,在上面的示例中,<TabGroup defaultIndex={5}>
将呈现第三个面板为选中状态。)
要每当选中的选项卡发生变化时运行一个函数,请在 TabGroup
组件上使用 onChange
道具。
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
function Example() {
return (
<TabGroup
onChange={(index) => { console.log('Changed selected tab to:', index) }} >
<TabList>
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
<Tab>Tab 3</Tab>
</TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
<TabPanel>Content 3</TabPanel>
</TabPanels>
</TabGroup>
)
}
默认情况下,选项卡组件在内部管理选中的选项卡。但是,您可以使用 selectedIndex
道具和 onChange
回调自己控制选中的选项卡
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
import { useState } from 'react'
function Example() {
const [selectedIndex, setSelectedIndex] = useState(0)
return (
<TabGroup selectedIndex={selectedIndex} onChange={setSelectedIndex}> <TabList>
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
<Tab>Tab 3</Tab>
</TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
<TabPanel>Content 3</TabPanel>
</TabPanels>
</TabGroup>
)
}
默认情况下,TabGroup
及其子组件分别渲染一个对该组件来说合理的默认元素。
例如,TabGroup
渲染一个 div
,TabList
渲染一个 div
,Tab
渲染一个 button
。
使用 as
道具将组件渲染为不同的元素或您自己的自定义组件,确保您的自定义组件 转发 ref,以便无头 UI 可以正确地连接它们。
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
import { forwardRef } from 'react'
let MyCustomButton = forwardRef(function (props, ref) { return <button className="..." ref={ref} {...props} />})
function Example() {
return (
<TabGroup>
<TabList as="aside"> <Tab as={MyCustomButton}>Tab 1</Tab>
<Tab as={MyCustomButton}>Tab 2</Tab>
<Tab as={MyCustomButton}>Tab 3</Tab>
</TabList>
<TabPanels as="section"> <TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
<TabPanel>Content 3</TabPanel>
</TabPanels>
</TabGroup>
)
}
要指示元素直接渲染其子元素,而无需包装元素,请使用 Fragment
。
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
import { Fragment } from 'react'
function Example() {
return (
<TabGroup as={Fragment}> <TabList>
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
<Tab>Tab 3</Tab>
</TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
<TabPanel>Content 3</TabPanel>
</TabPanels>
</TabGroup>
)
}
当 Tab
组件获得焦点时,所有交互都会应用。
命令 | 描述 |
向左箭头 和 向右箭头 | 选择上一个/下一个未禁用的选项卡。 |
向上箭头 和 向下箭头 当 | 选择上一个/下一个未禁用的选项卡。 |
Home 或 PageUp | 选择第一个未禁用的选项卡。 |
End 或 PageDown | 选择最后一个未禁用的选项卡。 |
Enter 或 Space 当 | 激活选中的标签。 |
属性 | 默认值 | 描述 |
as | div | String | 组件 要渲染为的元素或组件标签组应渲染为。 |
defaultIndex | 0 | Number 默认选中的索引 |
selectedIndex | — | number 如果你想将标签组件用作受控组件,则为选中的索引。 |
onChange | — | (index: number) => void 每当选中的标签发生改变时就会调用此函数。 |
vertical | false | Boolean 如果为真,则 |
manual | false | Boolean 如果为真,则用户只能通过键盘首先使用箭头键导航到面板,然后按 |
数据属性 | 渲染道具 | 描述 |
— | selectedIndex |
当前选中的索引。 |
属性 | 默认值 | 描述 |
as | div | String | 组件 要渲染为的元素或组件标签列表应渲染为。 |
数据属性 | 渲染道具 | 描述 |
— | selectedIndex |
当前选中的索引。 |
属性 | 默认值 | 描述 |
as | 片段 | String | 组件 要渲染为的元素或组件标签应渲染为。 |
disabled | false | Boolean 是否标签已禁用. |
autoFocus | false | Boolean 是否标签首次渲染时应接收焦点。 |
数据属性 | 渲染道具 | 描述 |
data-selected | selected |
是否标签已选中。 |
data-focus | focus |
是否标签已获得焦点。 |
data-hover | hover |
是否标签被悬停。 |
data-active | active |
是否标签处于活动状态或按下状态。 |
data-autofocus | autofocus |
|
属性 | 默认值 | 描述 |
as | div | String | 组件 要渲染为的元素或组件标签面板应渲染为。 |
数据属性 | 渲染道具 | 描述 |
— | selectedIndex |
当前选中的索引。 |
属性 | 默认值 | 描述 |
as | div | String | 组件 要渲染为的元素或组件标签面板应渲染为。 |
static | false | Boolean 元素是否应忽略内部管理的打开/关闭状态。 |
unmount | true | Boolean 元素是否应根据打开/关闭状态卸载或隐藏。 |
数据属性 | 渲染道具 | 描述 |
data-selected | selected |
是否标签面板已选中。 |
如果你对预先设计好的 Tailwind CSS 标签组件示例 感兴趣,请查看 Tailwind UI——一个由我们精心设计和制作的精美组件集合。
这是一种支持我们对诸如此类的开源项目的贡献的好方法,它使我们能够改进这些项目并保持其良好的维护。