Tabs
The Tabs component organizes content into multiple panels, each associated with a tab trigger. It comes in two modes: regular tabs that show and hide panels, and navigation tabs (Tabs.NavRoot) that integrate with your router.
It is composed from Tabs.Root, Tabs.NavRoot, Tabs.List, Tabs.Trigger, and Tabs.Content, so you assemble exactly the structure you need.
Variations
Variants
Apply a visual style with the variant prop on Tabs.List.
With icons
Tab triggers can include icons to enhance visual recognition and improve user experience.
With logos
Tab triggers can display logos for brand-specific navigation or integration tabs.
With counters
Tab triggers can display counter badges to show quantities or notifications.
Complex children
When icons, logos, and counters aren’t sufficient, you can use custom children for more complex tab triggers.
Navigation tabs
The Tabs.NavRoot variant integrates with your router to create navigation-based tabs that update based on the current route.
Controlled tabs
Both Tabs.Root and Tabs.NavRoot can be controlled by passing value and onValueChange props.
Orientation
The Tabs component supports both horizontal (default) and vertical orientations via the orientation prop on Tabs.Root. This affects both the visual layout and keyboard navigation behavior.
Scrollable tabs
When you have many tabs that overflow the container, the component automatically enables horizontal scrolling with fade indicators on both ends. The active tab will automatically scroll into view on mount and when changed.
Custom active class
You can customize the active tab styling using the activeClassName prop on Tabs.List.
Installation
import { Tabs } from "@harnessio/ui/components";
<Tabs.Root defaultValue="general"> <Tabs.List> <Tabs.Trigger value="general">General</Tabs.Trigger> <Tabs.Trigger value="security">Security</Tabs.Trigger> <Tabs.Trigger value="advanced">Advanced</Tabs.Trigger> </Tabs.List>
<Tabs.Content value="general">{/* General settings */}</Tabs.Content> <Tabs.Content value="security">{/* Security settings */}</Tabs.Content> <Tabs.Content value="advanced">{/* Advanced settings */}</Tabs.Content></Tabs.Root>;Anatomy
All parts of the Tabs component can be imported and composed as required. Regular tabs pair a List of triggers with matching Content panels; navigation tabs use NavRoot and drive routing from each trigger, so panels are usually rendered by the router instead.
// Regular tabs<Tabs.Root> <Tabs.List> <Tabs.Trigger /> </Tabs.List> <Tabs.Content /></Tabs.Root>
// Navigation tabs<Tabs.NavRoot> <Tabs.List> <Tabs.Trigger /> </Tabs.List></Tabs.NavRoot>Tabs.Listholds the triggers and carries the visualvariant. It automatically enables overflow scrolling with fade indicators and scrolls the active tab into view.Tabs.Triggeris a single tab. It inherits its variant from theList, and can show anicon, alogo, acounterbadge, or fully customchildren.Tabs.Contentis the panel shown for the active tab in regular tabs. In navigation tabs it renders as a plain container whose visibility you control through routing.
Best practices
Do
- Give every trigger a unique
value, and provide either adefaultValueor a controlledvalueso a tab is selected on first render. - Use descriptive trigger text so the tabs are understandable on their own.
- Reach for
Tabs.NavRootwhen tabs represent different routes in your application. - Let the component handle overflow — many tabs will scroll horizontally with fade indicators rather than wrapping.
Don’t
- Don’t put both an
iconand alogoon the same trigger; they are mutually exclusive. - Don’t set
variantper trigger to differ within one list — apply it once on theListso the group stays consistent.
Accessibility
Keyboard
- In regular tabs, arrow keys move between triggers with a roving tabindex: ←/→ for horizontal and ↑/↓ for vertical, with Home/End jumping to the first/last tab.
- Disabled triggers are skipped during keyboard navigation.
Labeling
- Regular tabs render
role="tablist"withrole="tab"triggers and a matchingtabpanel, wired up by the underlying primitive. - Navigation tabs render the
Listas a<nav>element with each trigger as a link, so they’re announced as navigation. - Use clear, human-readable trigger text; for icon- or logo-only triggers, make sure the trigger still has an accessible name.
Focus & states
- The active content panel is given
tabIndex={-1}so focus can move into it programmatically. disabledtriggers can’t be focused or activated. In navigation mode a disabled trigger getsaria-disabledand its navigation is prevented.
Known limitations
Tabs.NavRootis a context provider that only uses itschildren—value,onValueChange,defaultValue,orientation, andclassNamepassed to it are ignored. Drive the active tab from the current route through eachTrigger.- For navigation tabs,
aria-currenton the active tab comes from the router’sNavLink, so wire up your router (e.g. react-router) for it to appear.
Properties
Root
The main container for regular tabs. It manages the active tab state and provides context to child components.
Prop | Required | Default | Type |
|---|---|---|---|
| defaultValue | false | string | |
| value | false | string | |
| onValueChange | false | (value: string) => void | |
| orientation | false | 'horizontal' | 'horizontal' | 'vertical' |
| className | false | string | |
| children | true | ReactNode |
NavRoot
The container for navigation-based tabs. It provides routing context to its triggers; the active tab is derived from the current route rather than from local state.
Prop | Required | Default | Type |
|---|---|---|---|
| children | true | ReactNode |
List
A container for tab triggers that handles layout and the visual variant. In regular tabs it renders as a tablist; in navigation tabs it renders as a <nav>.
Prop | Required | Default | Type |
|---|---|---|---|
| variant | false | 'underlined' | 'underlined' | 'overlined' | 'ghost' | 'outlined' |
| activeClassName | false | string | |
| className | false | string | |
| children | true | ReactNode |
Trigger
An individual tab button (regular tabs) or navigation link (nav tabs). It inherits its variant from the List.
Prop | Required | Default | Type |
|---|---|---|---|
| value | true | string | |
| icon | false | IconName | |
| logo | false | LogoName | |
| counter | false | number | null | |
| badge | false | string | TabBadgeProps | |
| disabled | false | false | boolean |
| linkProps | false | Omit<NavLinkProps, 'to'> | |
| exact | false | boolean | |
| className | false | string | |
| children | false | ReactNode |
Content
The panel content associated with a tab. In regular tabs the primitive mounts only the active panel (unless forceMount is set); in navigation tabs it renders as a plain container.
Prop | Required | Default | Type |
|---|---|---|---|
| value | true | string | |
| className | false | string | |
| children | true | ReactNode |