Skip to content
VEXA
Navigation & menus

Tabs

Client leaf

Switch between related panels.

Live preview

Rendered from the installed VEXA source — the same files you receive.

A summary of your workspace and recent usage.

Installation

Adds the component source to your project. Registry dependencies are pulled in automatically.

npx shadcn@latest add https://vexa.valfiguer.com/r/tabs.json

Installs to components/vexa/tabs.tsx

npm dependencies

  • @radix-ui/react-tabs

Registry dependencies

  • @vexa/utils

Other VEXA items this component needs. Install them first — they are resolved automatically by the CLI.

Source

Copy this into its target path, or install it with the command above.

components/vexa/tabs.tsx
"use client";

import type * as React from "react";
import * as TabsPrimitive from "@radix-ui/react-tabs";

import { cn } from "@/lib/utils";

/**
 * Tabs — switch between related panels.
 *
 * Client leaf. Radix implements the APG tabs pattern: roving `Tab`/`Shift+Tab`
 * focus onto the active trigger, Arrow keys to move between triggers (Home/End
 * to jump), and `tab`/`tabpanel` roles with `aria-selected`/`aria-controls`
 * wired up. Automatic activation is the default; set `activationMode="manual"`
 * for panels whose content is expensive to render.
 */
export function Tabs({
  className,
  ...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
  return (
    <TabsPrimitive.Root
      data-slot="tabs"
      className={cn("flex flex-col gap-3", className)}
      {...props}
    />
  );
}

export function TabsList({
  className,
  ...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
  return (
    <TabsPrimitive.List
      data-slot="tabs-list"
      className={cn(
        "inline-flex h-10 w-fit items-center justify-center gap-1 rounded-vexa-md bg-vexa-muted p-1 text-vexa-muted-foreground",
        className,
      )}
      {...props}
    />
  );
}

export function TabsTrigger({
  className,
  ...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
  return (
    <TabsPrimitive.Trigger
      data-slot="tabs-trigger"
      className={cn(
        "inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-vexa-sm px-3 py-1.5 text-sm font-medium",
        "transition-[color,background-color,box-shadow] duration-[var(--vexa-duration-fast)] ease-[var(--vexa-ease-standard)]",
        "outline-none focus-visible:ring-2 focus-visible:ring-vexa-ring focus-visible:ring-offset-2 focus-visible:ring-offset-vexa-background",
        "disabled:pointer-events-none disabled:opacity-50",
        "data-[state=active]:bg-vexa-surface-raised data-[state=active]:text-vexa-foreground data-[state=active]:shadow-vexa-sm",
        "[&_svg]:size-4 [&_svg]:shrink-0",
        className,
      )}
      {...props}
    />
  );
}

export function TabsContent({
  className,
  ...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
  return (
    <TabsPrimitive.Content
      data-slot="tabs-content"
      className={cn(
        "outline-none focus-visible:ring-2 focus-visible:ring-vexa-ring focus-visible:ring-offset-2 focus-visible:ring-offset-vexa-background",
        className,
      )}
      {...props}
    />
  );
}

Details

Example

tsx
<Tabs defaultValue="a"><TabsList><TabsTrigger value="a">Account</TabsTrigger></TabsList><TabsContent value="a">...</TabsContent></Tabs>

Accessibility

APG tabs: roving Tab focus onto the active trigger, Arrow/Home/End to move between triggers, and tab/tabpanel roles with aria-selected/aria-controls.