rapidUI

Tabs

Tabbed views built on Base UI Tabs.

Installation

pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/tabs.json

Installs to @/components/ui/tabs.

Manual installation

Install the dependencies:

pnpm add @base-ui/react@^1.8.0

Copy the source into your project:

components/ui/tabs.tsx
'use client'

import { Tabs as TabsPrimitive } from '@base-ui/react/tabs'
import { cn } from '@/lib/utils'

function Tabs({ className, orientation = 'horizontal', ...props }: TabsPrimitive.Root.Props) {
	return (
		<TabsPrimitive.Root
			data-slot="tabs"
			data-orientation={orientation}
			orientation={orientation}
			className={cn('group/tabs flex gap-2 data-horizontal:flex-col', className)}
			{...props}
		/>
	)
}

function TabsList({ className, ...props }: TabsPrimitive.List.Props) {
	return (
		<TabsPrimitive.List
			data-slot="tabs-list"
			className={cn(
				'inline-flex w-fit items-center group-data-vertical/tabs:flex-col group-data-vertical/tabs:items-stretch',
				className,
			)}
			{...props}
		/>
	)
}

function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
	return (
		<TabsPrimitive.Tab
			data-slot="tabs-trigger"
			className={cn(
				// Rapid underline tabs with shadcn sizing: a muted line under every tab, a thicker primary line on the
				// active tab, and primary text on hover and when active (solid text-primary for 4.5:1 contrast)
				"relative inline-flex h-10 cursor-pointer items-center justify-center gap-1.5 rounded-md px-3 text-sm font-medium whitespace-nowrap text-foreground transition-colors outline-none hover:text-primary focus-visible:ring-3 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:font-semibold data-active:text-primary group-data-vertical/tabs:justify-start has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
				'after:absolute after:rounded-none after:bg-muted after:transition-colors after:duration-200 hover:after:bg-primary/90 data-active:after:bg-primary data-active:hover:after:bg-primary/90',
				'group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-0 group-data-horizontal/tabs:after:h-0.5 group-data-horizontal/tabs:hover:after:h-[3px] group-data-horizontal/tabs:data-active:after:h-[3px]',
				'group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:left-0 group-data-vertical/tabs:after:w-0.5',
				className,
			)}
			{...props}
		/>
	)
}

function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
	return (
		<TabsPrimitive.Panel
			data-slot="tabs-content"
			className={cn('flex-1 rounded-md text-sm outline-none focus-visible:ring-3 focus-visible:ring-ring', className)}
			{...props}
		/>
	)
}

export { Tabs, TabsList, TabsTrigger, TabsContent }

A tabbed interface component built on top of Base UI Tabs, providing an accessible way to organize and switch between different views.

Features

  • Underline style: a thin muted line under every tab, and a thicker primary line with semibold primary text on the active tab; hovered tabs turn primary
  • Horizontal and vertical orientation (orientation="vertical" on Tabs)
  • Icons in triggers, sized automatically; mark them with data-icon="inline-start" or data-icon="inline-end" for tighter padding
  • Controlled (value / onValueChange) and uncontrolled (defaultValue) modes
  • Disabled triggers
  • Keyboard navigation and screen reader support

Usage

import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'

export function TabsDemo() {
  return (
    <Tabs defaultValue="overview" className="w-full max-w-md">
      <TabsList>
        <TabsTrigger value="overview">Overview</TabsTrigger>
        <TabsTrigger value="history">History</TabsTrigger>
      </TabsList>
      <TabsContent value="overview">
        Case details…
      </TabsContent>
      <TabsContent value="history">
        Events in the case…
      </TabsContent>
    </Tabs>
  )
}

Examples

Default

A case view split into overview, applicant and history, each in a card.

Vertical

Set orientation="vertical" on Tabs to stack the triggers beside the content; the line moves to the left edge. Here as the settings menu on a resident's My pages.

With Icons

Icons with data-icon="inline-start" in the triggers, and a count badge on the messages tab of a guardian's school view.

Icon Only

Triggers with only an icon. Give each trigger an aria-label, and wrap it in a Tooltip so sighted users see the name too.

Controlled

Controlled with value / onValueChange, so Back and Next buttons can move through the steps of an application.

Disabled State

A disabled trigger for a step that isn't available yet – appeals open once a decision has been made.

Props

Tabs

  • defaultValue / value / onValueChange - Uncontrolled or controlled active tab; onValueChange(value, eventDetails)
  • orientation - 'horizontal' | 'vertical' (default 'horizontal')

TabsList

  • activateOnFocus - Activate a tab as soon as it receives focus with the arrow keys (default false: arrow keys move focus, Enter / Space activate)
  • loopFocus - Whether arrow keys wrap from the last tab to the first (default true)
  • All Base UI Tabs.List props

TabsTrigger

  • value - The value of the tab
  • disabled - Whether the tab is disabled

TabsContent

  • value - The value of the tab this panel belongs to
  • keepMounted - Keep the panel in the DOM while it is hidden

Anatomy

<Tabs>
  <TabsList>
    <TabsTrigger />
  </TabsList>
  <TabsContent />
</Tabs>

Accessibility

The Tabs component follows WAI-ARIA guidelines:

  • Uses proper ARIA roles (tablist, tab, tabpanel)
  • Keyboard navigation:
    • Tab: Move focus to the next focusable element
    • Arrow keys: Navigate between tabs
    • Space/Enter: Activate tab
    • Home/End: Go to first/last tab
  • Focus management within panels
  • Screen reader announcements
  • Clear visual feedback
  • Proper labeling

Best Practices

  1. Content Organization:

    • Group related content
    • Use clear labels
    • Keep content focused
    • Consider tab order
  2. Visual Design:

    • Maintain clear hierarchy
    • Use consistent spacing
    • Provide visual feedback
    • Consider mobile layout
  3. Interaction:

    • Support keyboard navigation
    • Handle focus management
    • Provide clear feedback
    • Consider loading states
  4. Accessibility:

    • Use descriptive labels
    • Maintain focus order
    • Support screen readers
    • Consider keyboard users
  5. Implementation:

    • Handle state changes
    • Manage content updates
    • Consider performance
    • Test edge cases

Customization

Custom Styling

Modify the appearance using Tailwind classes:

<Tabs className="w-full">
  <TabsList className="grid w-full grid-cols-2">
    <TabsTrigger
      value="tab1"
      className="data-active:bg-primary data-active:text-primary-foreground"
    >
      Tab 1
    </TabsTrigger>
  </TabsList>
  <TabsContent
    value="tab1"
    className="rounded-lg border p-4"
  >
    Content 1
  </TabsContent>
</Tabs>

Vertical Orientation

Create vertically oriented tabs:

<Tabs defaultValue="account" orientation="vertical">
  <TabsList>
    <TabsTrigger value="account">Account</TabsTrigger>
    <TabsTrigger value="password">Password</TabsTrigger>
  </TabsList>
  <TabsContent value="account">
    Account settings...
  </TabsContent>
  <TabsContent value="password">
    Password settings...
  </TabsContent>
</Tabs>

With Icons

Add icons to tab triggers:

<TabsTrigger value="settings">
  <Settings2Icon data-icon="inline-start" />
  Settings
</TabsTrigger>
  • Accordion - For vertically stacked sections
  • Sub Tabs - For secondary tab navigation
  • Card - For content containers
  • Navigation Menu - For site navigation