Raft 设计系统

组件 · 导航

Tabs

A row of tabs for the sections of one page, each with its own content; the selected tab is a yellow sticker, and the row scrolls when the tabs don't fit.

稳定
import { Tabs } from "@botiverse/raft-ui";
本页尚未翻译
译文完成之前,这里显示英文原文。 阅读英文版

Tabs switch between the sections of one page, each with its own content: a channel's chat, tasks and files, for example. The selected tab is a yellow sticker, the same "you are here" as a sidebar row or a segmented control. When the tabs don't fit, the row scrolls sideways.

tabs/compositions/placement

When to use

SituationUse
Sections of one page, each with its own contentTabs
Another view of the same contentSegmentedControl
The steps of a flow, in orderSteps
A product's destinationsAppRail, MobileNav

A place with several kinds of content, such as a channel, shows them as one row of sticker tabs under its header (Navigation).

Anatomy

  • Tabs: the root. It holds the selected value and the placement.
  • TabsList: the row of tabs.
  • TabsTab: one tab, with an optional icon before a TabsLabel.
  • TabsPanel: the content of one tab.
  • SortableTabsList and SortableTabsTab: a row whose tabs people can reorder by dragging. useOrderedTabs restores a saved order.

Variants

VariantUse for
variant="default"The sticker tab: the selected tab is a yellow sticker. The default
variant="underline"Many tabs, or a place that already has a lot of color: the selected tab is ink text with a 2px ink line on the row's bottom edge

The sticker tab is the default, and the one the product ships: a selected tab says "you are here", like a selected sidebar row or segment, so it takes the same yellow sticker (Navigation). The underline stays a variant. placement puts the row on the top (the default), bottom, left or right of the panels.

Behavior

  • When the tabs don't fit, the row scrolls on its own. Tabs never wrap onto a second line and never shrink their text.
  • The row shows no scrollbar, and a vertical mouse wheel scrolls it sideways.
  • The side that hides tabs fades out over 32px.
  • The selected tab always scrolls into view.

A tab's own controls

When a tab's content has controls of its own (a view switch, filters, search, a sort), they go in one toolbar row at the top of the tab's content, under the tabs. Every tab with controls uses the same row, the one the Tasks tab draws (TasksPanelToolbar), so a person who has learned one tab knows where to look in the next:

  • The row has its own height and padding, the content margin at its sides and a line-seam below it, and it stays pinned while the content scrolls under it. Content never starts flush under the row, and never scrolls up against the tabs.
  • Filters and search start at the row's left; a SegmentedControl that switches the view goes at its right end. A view switch on the left would sit right under the selected tab, and two yellow stickers stacked one above the other read as the same level.
  • A tab with no controls has no row.

Content

  • Label tabs with 1 or 2 words in Title Case: Chat, Tasks, Files (Typography).
  • Put a count in its own Space Mono element after the label, never in the label's text.

Accessibility

  • Tabs wraps the Base UI Tabs, which provides the tab list, tab and panel semantics and the arrow keys.
  • Name the TabsList after the place it belongs to: aria-label="launch-week".
  • Give every icon-only tab an aria-label.
  • Sortable tabs are reordered with a pointer or by touch; there is no keyboard reordering.

Rules

  • Use one row of tabs per screen.
  • Make a sticker tab 32px high, with a 44px hit area and 8px of row above and below it, so the selected sticker sits as far from the line above as from the line below. Its width follows its label, and it has no track behind it.
  • Make an underline tab 44px high, 20px from the next, with text only.
  • Draw a line-seam under the row in both variants. When you change the size of the text or the row, check that the selected tab's space above and below is still equal.
  • Start the first sticker on the content's edge, the same margin as the panel header's first element (Page structure). In dark mode the selected sticker has the die-cut edge like every sticker, so it lines up with the avatar in the header (Dark mode).

API

SortableTabsList

Prop类型默认值
onDragStartA tab was picked up. On a touch screen the long press that armed it may have opened a menu the caller owns: close it here (DS › States › Dragging on a touch screen).((event: BaseUIEvent<DragEvent<HTMLDivElement>>) => void) & ((value: T) => void)
onReorder (required)(value: T[]) => void
value (required)readonly T[]
variantTabsListVariant"default"
activateOnFocusWhether to automatically change the active tab on arrow key focus. Otherwise, tabs will be activated using <kbd>Enter</kbd> or <kbd>Space</kbd> key press.booleanfalse
loopFocusWhether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys.booleantrue
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

SortableTabsTab

Prop类型默认值
value (required)The value of the Tab.any
disabledWhether the Tab is disabled.boolean
nativeButtonWhether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`).booleantrue
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

Tabs

Prop类型默认值
defaultValueT
onValueChange(value: T, eventDetails: BaseTabs.Root.ChangeEventDetails) => void
placementSide of the panels the tab list sits on. `left` and `right` also make the tabs vertical (arrow up / down) unless `orientation` is set.TabsPlacement"top"
valueT
variant"default" | "underline""default"
orientationThe component orientation (layout flow direction).Orientation'horizontal'
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

TabsLabel

接受其元素的全部属性。

TabsList

Prop类型默认值
variantTabsListVariant"default"
activateOnFocusWhether to automatically change the active tab on arrow key focus. Otherwise, tabs will be activated using <kbd>Enter</kbd> or <kbd>Space</kbd> key press.booleanfalse
loopFocusWhether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys.booleantrue
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

TabsPanel

Prop类型默认值
keepMountedWhether to keep the HTML element in the DOM while the panel is hidden.booleanfalse
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)
value (required)The value of the TabPanel. It will be shown when the Tab with the corresponding value is active.any

Also takes the attributes of its element.

TabsTab

Prop类型默认值
value (required)The value of the Tab.any
disabledWhether the Tab is disabled.boolean
nativeButtonWhether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`).booleantrue
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

改动

  • 0.5.36 · ChangedElegant Tabs underline: the bar starts on the 20px line at every width (the first icon lands at 32), and the strip has one source now — the indicator defaults to bg-primary-400 and the placement blocks no longer draw a static hairline, so the conversation panel, the agent-details strip and the docs read the same look. Brutal underline: the conversation panel drops its asymmetric first-tab pl-[22px]; the strip sits at 16 and reads centred, as the agent-details strip.