Tabs

Tabs organize content across different screens and views

Usage

Tabs organize groups of related content that are at the same level of hierarchy.

Instalation

Run the following command:

npm i @radix-ui/react-tabs

Copy and paste the following code into your project.

'use client'

import * as React from 'react'
import * as TabsPrimitive from '@radix-ui/react-tabs'

import { cn } from '@/lib/utils'

const TabsRoot = React.forwardRef<
  React.ElementRef<typeof TabsPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>
>(({ className, ...props }, ref) => (
  <TabsPrimitive.Root
    ref={ref}
    className={cn('w-full', className)}
    {...props}
  />
))
TabsRoot.displayName = TabsPrimitive.Root.displayName

const TabsList = React.forwardRef<
  React.ElementRef<typeof TabsPrimitive.List>,
  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
  <TabsPrimitive.List
    ref={ref}
    className={cn(
      'inline-flex max-h-16 min-h-12 w-full items-stretch justify-evenly bg-surface shadow-sm',
      className
    )}
    {...props}
  />
))
TabsList.displayName = TabsPrimitive.List.displayName

const TabsTrigger = React.forwardRef<
  React.ElementRef<typeof TabsPrimitive.Trigger>,
  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
  <TabsPrimitive.Trigger
    ref={ref}
    className={cn(
      'group relative z-0 inline-flex w-full min-w-fit flex-col items-center justify-center whitespace-nowrap px-4 text-title-sm text-onSurfaceVariant outline-none transition-colors focus:outline-none',
      'data-[state=active]:text-primary',
      'disabled:pointer-events-none',
      className
    )}
    {...props}
  >
    {children}
    <span className="indicator absolute bottom-0 mx-auto mt-auto hidden h-1 w-1/3 min-w-4 animate-zoom-in-x rounded-t-sm bg-primary group-data-[state=active]:block" />
    <span className="state-layer absolute inset-0 z-[-1] bg-onSurface opacity-0 transition-opacity group-hover:opacity-4 group-focus:opacity-4 group-focus-visible:opacity-8 group-data-[state=active]:bg-primary group-data-[state=active]:opacity-0 group-data-[state=active]:group-hover:opacity-4" />
  </TabsPrimitive.Trigger>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName

const TabsContent = React.forwardRef<
  React.ElementRef<typeof TabsPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
  <TabsPrimitive.Content
    ref={ref}
    className={cn(
      'focus-visible:ring-ring mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
      className
    )}
    {...props}
  />
))
TabsContent.displayName = TabsPrimitive.Content.displayName

const Tabs = Object.assign(TabsRoot, {
  List: TabsList,
  Trigger: TabsTrigger,
  Content: TabsContent,
})

export { Tabs, TabsList, TabsTrigger, TabsContent }

Update the import paths to match your project setup.

Examples

Primary Tabs

Primary tabs are placed at the top of the content pane under a top app bar. They display the main content destinations.

Flights

export const PrimaryTabsWithIcons = () => {
  return (
    <Tabs defaultValue="flights">
      <Tabs.List className="h-16">
        <Tabs.Trigger value="flights">
          <Icon symbol="flight" />
          Flights
        </Tabs.Trigger>
        <Tabs.Trigger value="trips">
          <Badge content="3" size="large">
            <Icon symbol="trip" />
          </Badge>
          Trips
        </Tabs.Trigger>
        <Tabs.Trigger value="discover">
          <Icon symbol="explore" />
          Discover
        </Tabs.Trigger>
      </Tabs.List>
      <Tabs.Content value="flights" className="p-6">
        <h2 className="text-title-lg">Flights</h2>
      </Tabs.Content>
      <Tabs.Content value="trips" className="p-6">
        <h2 className="text-title-lg">Trips</h2>
      </Tabs.Content>
      <Tabs.Content value="discover" className="p-6">
        <h2 className="text-title-lg">Discover</h2>
      </Tabs.Content>
    </Tabs>
  )
}

Secondary Tabs

Secondary tabs are used within a content area to further separate related content and establish hierarchy.

Flights

export const SecondaryTabs = () => {
  return (
    <Tabs defaultValue="flights">
      <Tabs.List className="[&_.indicator]:w-full [&_.indicator]:rounded-none">
        <Tabs.Trigger value="flights">Flights</Tabs.Trigger>
        <Tabs.Trigger value="trips">
          <Badge content="3" size="large" className="static ml-2">
            Trips
          </Badge>
        </Tabs.Trigger>
        <Tabs.Trigger value="discover">Discover</Tabs.Trigger>
      </Tabs.List>
      <Tabs.Content value="flights" className="p-6">
        <h2 className="text-title-lg">Flights</h2>
      </Tabs.Content>
      <Tabs.Content value="trips" className="p-6">
        <h2 className="text-title-lg">Trips</h2>
      </Tabs.Content>
      <Tabs.Content value="discover" className="p-6">
        <h2 className="text-title-lg">Discover</h2>
      </Tabs.Content>
    </Tabs>
  )
}

Scrollable Tabs

When a set of tabs cannot fit on screen, use scrollable tabs. Scrollable tabs can use longer text labels and a larger number of tabs.

Optionaly you can add the utility class scrollbar-hidden to your main.css to hide the scrollbar.

Akita

export const ScrollableTabs = () => {
  return (
    <Tabs defaultValue="akita" className="max-w-sm overflow-hidden">
      <div className="scrollbar-hidden max-w-full overflow-auto pl-14">
        <Tabs.List className="flex-nowrap justify-start">
          <Tabs.Trigger value="akita">Akita</Tabs.Trigger>
          <Tabs.Trigger value="alaskan">Alaskan</Tabs.Trigger>
          <Tabs.Trigger value="australian_shepard">
            Australian Shepard
          </Tabs.Trigger>
          <Tabs.Trigger value="Azawakh">Azawakh</Tabs.Trigger>
          <Tabs.Trigger value="labrador-retriever">
            Labrador Retriever
          </Tabs.Trigger>
          <Tabs.Trigger value="french-bulldog">French Bulldog</Tabs.Trigger>
          <Tabs.Trigger value="beagle">Beagle</Tabs.Trigger>
          <Tabs.Trigger value="rottweiler">Rottweiler</Tabs.Trigger>
        </Tabs.List>
      </div>
      <Tabs.Content value="akita" className="p-6">
        <h2 className="text-title-lg">Akita</h2>
      </Tabs.Content>
      <Tabs.Content value="alaskan" className="p-6">
        <h2 className="text-title-lg">Alaskan</h2>
      </Tabs.Content>
      <Tabs.Content value="australian_shepard" className="p-6">
        <h2 className="text-title-lg">Australian Shepard</h2>
      </Tabs.Content>
      <Tabs.Content value="Azawakh" className="p-6">
        <h2 className="text-title-lg">Azawakh</h2>
      </Tabs.Content>
      <Tabs.Content value="labrador-retriever" className="p-6">
        <h2 className="text-title-lg">Labrador Retriever</h2>
      </Tabs.Content>
      <Tabs.Content value="french-bulldog" className="p-6">
        <h2 className="text-title-lg">French Bulldog</h2>
      </Tabs.Content>
      <Tabs.Content value="beagle" className="p-6">
        <h2 className="text-title-lg">Beagle</h2>
      </Tabs.Content>
      <Tabs.Content value="rottweiler" className="p-6">
        <h2 className="text-title-lg">Rottweiler</h2>
      </Tabs.Content>
    </Tabs>
  )
}
// main.css
@layer utilities {
  .scrollbar-hidden {
    /* Hide scrollbar for Chrome, Safari, and Opera */
    &::-webkit-scrollbar {
      display: none;
    }

    /* Hide scrollbar for IE, Edge, and Firefox */
    -ms-overflow-style: none; /* IE and Edge */
    scrollbar-width: none; /* Firefox */
  }
}