Skip to content

Tabs

Shows one of several panels in the same place. Use it where content is switched between rather than laid out side by side.

tsx
import { Tab, TabPanel, Tabs } from 'neba';

<Tabs defaultValue="overview">
  <Tab value="overview">Overview</Tab>
  <Tab value="usage">Usage</Tab>

  <TabPanel value="overview">Three deploys today, all green.</TabPanel>
  <TabPanel value="usage">1,284 build minutes used.</TabPanel>
</Tabs>;

There is no <TabList> wrapper. Write Tabs and TabPanels side by side and the component sorts them into the bar and the panel area.

Props

Tabs

PropTypeDefaultDescription
variantshared'solid' | 'outline' | 'text''outline'Weight of the tab **bar**, not of the panels. solid is a segmented control, outline is a rule along the bar, text is the same without it
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'The tabs' height and type scale, on Button's own ladder, so a tab bar keeps its baseline in a toolbar
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'Semantic colour role. Arbitrary colour values are not accepted
densityshared'default' | 'compact''default'Padding only: never the height, never the type scale
valuestring | number | nullThe chosen tab, for a controlled set
defaultValuestring | number | nullWhich starts chosen
onValueChange(value: string | number | null) => voidCalled when the chosen tab changes
orientationshared'horizontal' | 'vertical''horizontal'Which way the bar runs. vertical stands the tabs down the side and moves the arrow keys onto the other axis
activateOnFocusbooleanfalseWhether the arrow keys also choose. Off by default: the moment one panel fetches, walking past four tabs fires four requests
loopFocusbooleantrueWhether the arrow keys wrap from the last tab back to the first
overflow'scroll' | 'wrap''scroll'What a bar with more tabs than room does. scroll stays one line and fades the end that has more past it; wrap takes as many lines as it needs
linesnumberThe most tab-rows the bar may be tall; past it the bar scrolls. Only read when overflow is wrap
wheelbooleantrueTurns a wheel rolled over the bar into travel along it, and holds it at the ends too. A bar that fits takes nothing; the pointer leaving the bar is what gives the page its wheel back
fullWidthbooleanfalseThe tabs share the full width of the bar, each taking an equal part
childrenReactNodeThe Tabs and the TabPanels. They are sorted into their two boxes for you, so there is no list wrapper to remember

Tab

PropTypeDefaultDescription
value * string | numberIdentifies the tab, and picks out the panel with the same value
startIconReactNodeContent before the label
endIconReactNodeContent after the label: a count, a Badge, a status dot
disabledbooleanfalseUnavailable, but still listed
childrenReactNodeThe tab's label

TabPanel

PropTypeDefaultDescription
value * string | numberWhich tab shows this panel
keepMountedbooleanfalseKeeps the panel in the DOM while hidden. For content that is expensive to build, or form state that should survive
childrenReactNodeThe content

Examples

variant

variant is the weight of the tab bar, not of the panels under it.

  • solid: a filled tile moves between the tabs inside a trough.
  • outline: the indicator rides on a rule along the edge of the bar.
  • text: the indicator with no rule, for tabs inside a Card that already has an edge.

orientation

vertical puts the bar down the left side. The arrow keys move onto the vertical axis with it.

overflow and lines

overflow says what a bar with more tabs than room does about it. scroll (the default) keeps the bar on one line and scrolls along it; the ends fade while there is more bar in that direction, and the scrollbar itself is hidden. wrap takes as many lines as the tabs need, and the rule under the chosen tab moves onto the line that tab is on.

lines caps a wrapping bar at that many tab-rows and scrolls past the cap. It is read only when overflow is wrap.

wheel

A mouse has one wheel and it points down the page, which is the one axis a scrolling tab bar does not run along. wheel turns a wheel rolled over the bar into travel along it. It is on by default: the bar has no scroll buttons and its scrollbar is hidden, so a wheel the page answers instead leaves the tabs past the edge reachable only by keyboard.

What it takes it keeps, at the ends as well, so a flick that runs out of bar does not turn into a jump down the article. Moving the pointer off the bar is what gives the page its wheel back. A bar that fits takes nothing, so a page with three tabs on it is unaffected. A trackpad swiping sideways is left to the browser, which scrolls it better.

Pass wheel={false} to turn it off.

startIcon and endIcon

Put an icon or a count before or after the label.

size

The same control heights as Button: a md tab and a md button are both 32px, so a tab bar can sit in a toolbar beside one.

activateOnFocus

Off by default: walking the arrow keys along the bar does not change the panel, and Enter or Space activates. That keeps a panel that fetches from firing a request per tab passed.

keepMounted

Set on a TabPanel, it keeps an unselected panel's React tree alive.

Accessibility

  • The whole bar is one tab stop, with the arrow keys and Home/End moving within it (a roving tab index).
  • The tab / tabpanel roles and the aria-controls between them are wired up.
  • A panel with nothing focusable inside it takes focus itself, so the content stays reachable by keyboard.
  • The indicator moves via left / top and width / height, so no label is resampled.

Released under the MIT License