import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
export default function TabsDemo() {
return (
<Tabs defaultValue="account" className="w-[400px]">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account" className="space-y-4 pt-2">
<div className="grid gap-3">
<Label htmlFor="tabs-name">Name</Label>
<Input id="tabs-name" defaultValue="Pedro Duarte" />
</div>
<Button>Save changes</Button>
</TabsContent>
<TabsContent value="password" className="space-y-4 pt-2">
<div className="grid gap-3">
<Label htmlFor="tabs-password">Password</Label>
<Input id="tabs-password" type="password" />
</div>
<Button>Save password</Button>
</TabsContent>
</Tabs>
)
}Installation
npx logic2b@latest add tabsWorking with an AI assistant? Copy this prompt into Claude Code, Cursor or Copilot — it carries the install steps, a no-CLI fallback and a verification checklist.
Add the "tabs" item from the logic2b ui registry (https://ui.logic2b.com) to this project. Follow every step and verify at the end. ## Steps 1. If the project has no `components.json` yet, set up logic2b ui first: ```bash npx logic2b@latest init ``` 2. Install the item (the CLI resolves registry dependencies automatically): ```bash npx logic2b@latest add tabs ``` 3. Install any npm dependencies the command prints. ## If the CLI is not available Do it manually from the registry payload: 1. Fetch https://ui.logic2b.com/r/tabs.json. 2. Write every entry in `files[]` to its mapped project path (see below). 3. Repeat (recursively) for each name in `registryDependencies`. 4. Install every package listed in `dependencies` across those payloads. ## Registry reference - Index of every item: https://ui.logic2b.com/r/index.json - Item payload (full source): https://ui.logic2b.com/r/<name>.json - Usage examples per item: https://ui.logic2b.com/r/demos/index.json → https://ui.logic2b.com/r/demos/<demo>.json - Docs index for agents: https://ui.logic2b.com/llms.txt (full docs: https://ui.logic2b.com/llms-full.txt) - Every docs page is Markdown when you append `.md` to its URL. Registry file paths map to project paths like this: - `ui/*` → `@/components/ui/*` - `blocks/<name>/*` → `@/components/*` - `charts/*` → `@/components/charts/*` - `hooks/*` → `@/hooks/*` - `lib/utils.ts` → `@/lib/utils.ts` - `theme.css` → next to the project's Tailwind entry stylesheet ## Conventions (do not break these) - Tailwind CSS v4 (CSS-first config; no tailwind.config.js needed) and React 19. - All colors go through semantic tokens (`bg-primary`, `text-muted-foreground`, `border-border`, …). Never hardcode hex/oklch values in components. - Dark mode is class-based: toggle `.dark` on `<html>`. - Icons come from lucide-react. ## Verify before finishing 1. The dev server starts and the app renders without console errors. 2. TypeScript compiles (`tsc --noEmit` or the framework's check). 3. Components use the theme: a `<Button>` shows the primary token color, and toggling `.dark` on `<html>` switches the palette.
Install the dependencies:
npm install radix-uiCopy into ui/tabs.tsx:
"use client"
import * as React from "react"
import { Tabs as TabsPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
function TabsList({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
className
)}
{...props}
/>
)
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
{...props}
/>
)
}
export { Tabs, TabsList, TabsTrigger, TabsContent }Usage
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
<Tabs defaultValue="account">
<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>
API
| Prop | Type | Default |
|---|---|---|
defaultValue (Tabs) |
string |
— |
value / onValueChange (Tabs) |
string / (value: string) => void |
— |
orientation (Tabs) |
horizontal | vertical |
horizontal |
value (TabsTrigger / TabsContent) |
string — links a trigger to its panel |
— |
disabled (TabsTrigger) |
boolean |
false |
Examples
With icons
View your activity at a glance.
import { BarChartIcon, SettingsIcon, UserIcon } from "lucide-react"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
export default function TabsIconsDemo() {
return (
<Tabs defaultValue="overview" className="w-[400px]">
<TabsList>
<TabsTrigger value="overview">
<BarChartIcon />
Overview
</TabsTrigger>
<TabsTrigger value="account">
<UserIcon />
Account
</TabsTrigger>
<TabsTrigger value="settings">
<SettingsIcon />
Settings
</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="text-muted-foreground pt-2 text-sm">
View your activity at a glance.
</TabsContent>
<TabsContent value="account" className="text-muted-foreground pt-2 text-sm">
Manage your account details.
</TabsContent>
<TabsContent value="settings" className="text-muted-foreground pt-2 text-sm">
Configure your preferences.
</TabsContent>
</Tabs>
)
}Disabled tab
Items you are currently working on.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
export default function TabsDisabledDemo() {
return (
<Tabs defaultValue="active" className="w-[400px]">
<TabsList>
<TabsTrigger value="active">Active</TabsTrigger>
<TabsTrigger value="archived">Archived</TabsTrigger>
<TabsTrigger value="deleted" disabled>
Deleted
</TabsTrigger>
</TabsList>
<TabsContent value="active" className="text-muted-foreground pt-2 text-sm">
Items you are currently working on.
</TabsContent>
<TabsContent
value="archived"
className="text-muted-foreground pt-2 text-sm"
>
Items you have archived for later.
</TabsContent>
</Tabs>
)
}Accessibility
- Follows the WAI-ARIA tabs pattern:
Tabmoves focus into the active trigger, thenArrowkeys move between triggers. Home/Endjump to the first / last trigger; disabled triggers are skipped.- Each
TabsContentis exposed as atabpanellabelled by its trigger, so the relationship is announced.