Skip to content
← Components

Tabs

Tabs with thoughtful defaults, semantic styling, and editable source.

Open ↗

Make it yours.

Install the editable source and its dependencies into your React + Tailwind project.

Scroll horizontally for long lines
pnpm dlx shadcn@4.0.8 add http://localhost:3000/r/tabs.json

Use it

Scroll horizontally for long lines
"use client";
import {
  Tabs,
  TabsList,
  TabsTrigger,
  TabsContent,
} from "@/components/jez-ui/ui/tabs";

export default function Example() {
  const tabs = [
    {
      value: "design",
      label: "Design",
      content: "Make it feel like something.",
    },
    {
      value: "build",
      label: "Build",
      content: "Give a good idea a useful shape.",
    },
    { value: "share", label: "Share", content: "Put it into the world." },
  ];
  return (
    <>
      <Tabs defaultValue="design">
        <TabsList aria-label="Workflow">
          {tabs.map((t) => (
            <TabsTrigger
              key={t.value}
              value={t.value}
            >
              {t.label}
            </TabsTrigger>
          ))}
        </TabsList>
        {tabs.map((t) => (
          <TabsContent
            key={t.value}
            value={t.value}
          >
            {t.content}
          </TabsContent>
        ))}
      </Tabs>
    </>
  );
}

Compose your own

These styled parts are included in the same install. Use children to build your layout; add className only when you want an override.

Scroll horizontally for long lines
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/jez-ui/ui/tabs";
Read the composition guide →

Props & types

Scroll horizontally for long lines
React.ComponentProps<typeof P.Root> & {
  items?: { value: string; label: React.ReactNode; content: React.ReactNode }[];
}

Source

View 2 source files

registry/ui/tabs.tsx

Scroll horizontally for long lines
"use client";
import * as React from "react";
import { Tabs as P } from "radix-ui";
import { cn } from "./utils";
export function Tabs({
  items,
  children,
  className,
  ...props
}: React.ComponentProps<typeof P.Root> & {
  items?: { value: string; label: React.ReactNode; content: React.ReactNode }[];
}) {
  return (
    <P.Root
      defaultValue={items?.[0]?.value}
      className={cn("w-full", className)}
      {...props}
    >
      {items ? (
        <>
          <TabsList>
            {items.map((i) => (
              <TabsTrigger key={i.value} value={i.value}>
                {i.label}
              </TabsTrigger>
            ))}
          </TabsList>
          {items.map((i) => (
            <TabsContent key={i.value} value={i.value}>
              {i.content}
            </TabsContent>
          ))}
        </>
      ) : (
        children
      )}
    </P.Root>
  );
}
export function TabsList({
  className,
  ...props
}: React.ComponentProps<typeof P.List>) {
  return (
    <P.List
      className={cn("flex flex-wrap gap-1 border-b border-border", className)}
      {...props}
    />
  );
}
export function TabsTrigger({
  className,
  ...props
}: React.ComponentProps<typeof P.Trigger>) {
  return (
    <P.Trigger
      className={cn(
        "border-b-2 border-transparent px-4 py-3 text-sm outline-none hover:text-primary focus-visible:ring-2 focus-visible:ring-primary disabled:opacity-50 data-[state=active]:border-primary data-[state=active]:text-primary",
        className,
      )}
      {...props}
    />
  );
}
export function TabsContent({
  className,
  ...props
}: React.ComponentProps<typeof P.Content>) {
  return (
    <P.Content
      className={cn(
        "py-5 text-sm leading-relaxed outline-none focus-visible:ring-2 focus-visible:ring-primary",
        className,
      )}
      {...props}
    />
  );
}

registry/ui/utils.ts

Scroll horizontally for long lines
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...values: ClassValue[]) {
  return twMerge(clsx(values));
}

Dependencies

radix-ui@1.6.7 · clsx@2.1.1 · tailwind-merge@3.5.0

Accessibility & behaviour

Provide meaningful labels and preserve keyboard focus styles. Check contrast when customising theme colours.

Read the accessibility and performance guide →