{
  "schemaVersion": 1,
  "target": "react",
  "name": "tabs",
  "version": "0.2.0",
  "category": "core",
  "title": "Tabs",
  "description": "Switch between related panels with an underline that follows the active tab on a hairline, sliding on click and jumping on keyboard selection.",
  "tags": [
    "tabs",
    "navigation",
    "panels",
    "base-ui"
  ],
  "requires": {
    "projectTypes": [
      "next",
      "vite-react"
    ],
    "styling": "tailwind-v4"
  },
  "npmDependencies": {
    "@base-ui/react": "^1.9.0"
  },
  "registryDependencies": [],
  "docsPath": "/components/tabs",
  "props": [
    {
      "name": "Tabs.defaultValue",
      "type": "any",
      "default": "0",
      "description": "Value of the tab that is active initially, when uncontrolled."
    },
    {
      "name": "Tabs.value",
      "type": "any",
      "description": "Value of the active tab, when controlled. Pair with onValueChange."
    },
    {
      "name": "Tabs.onValueChange",
      "type": "(value: any, details) => void",
      "description": "Called when the active tab changes."
    },
    {
      "name": "Tabs.orientation",
      "type": "\"horizontal\" | \"vertical\"",
      "default": "\"horizontal\"",
      "description": "Lays the tabs out in a row or a column and sets which arrow keys move between them."
    },
    {
      "name": "TabsList.activateOnFocus",
      "type": "boolean",
      "default": "false",
      "description": "Activates a tab as soon as arrow keys focus it, instead of on Enter or Space."
    },
    {
      "name": "TabsList.loopFocus",
      "type": "boolean",
      "default": "true",
      "description": "Wraps arrow-key focus from the last tab to the first and back."
    },
    {
      "name": "TabsTab.value",
      "type": "any",
      "description": "Identifies the tab. Must match a TabsPanel value."
    },
    {
      "name": "TabsTab.disabled",
      "type": "boolean",
      "default": "false",
      "description": "Skips the tab during keyboard navigation and ignores clicks."
    },
    {
      "name": "TabsPanel.value",
      "type": "any",
      "description": "Shown while the tab with the same value is active."
    },
    {
      "name": "TabsPanel.keepMounted",
      "type": "boolean",
      "default": "false",
      "description": "Keeps the panel in the DOM, hidden, while another tab is active."
    }
  ],
  "accessibility": [
    "Base UI renders the tablist, tab, and tabpanel roles and links each tab to its panel with aria-controls and aria-labelledby.",
    "Arrow keys move focus between tabs (Left/Right, or Up/Down when vertical); Home and End jump to the first and last tab.",
    "Only the active tab is in the tab order, so Tab moves from the tab list straight into the panel.",
    "The active tab is marked with aria-selected and an underline bar, not only a color change.",
    "Give TabsList an aria-label that names the group.",
    "The underline slides with a 350ms spring only for pointer selection; keyboard selection moves it without animating, and with reduced motion it never slides. Panels fade in over 150ms, removed with reduced motion."
  ],
  "states": [
    "default",
    "hover",
    "active",
    "focus-visible",
    "disabled"
  ],
  "files": [
    {
      "path": "ui/tabs.tsx",
      "content": "\"use client\";\n\nimport { Tabs as BaseTabs } from \"@base-ui/react/tabs\";\nimport { type KeyboardEvent, type PointerEvent, useState } from \"react\";\n\nfunction cx(...classes: Array<string | false | null | undefined>) {\n  return classes.filter(Boolean).join(\" \");\n}\n\n// A critically damped spring (bounce 0) settling in about 350ms, sampled for CSS linear().\nconst spring =\n  \"ease-[linear(0,0.044,0.144,0.263,0.384,0.495,0.593,0.675,0.744,0.8,0.844,0.88,0.908,0.929,0.946,0.959,0.969,0.977,0.982,0.987,1)]\";\n\nexport interface TabsProps extends Omit<BaseTabs.Root.Props, \"className\"> {\n  className?: string;\n}\n\n/**\n * Groups a tab list with its panels. Set `defaultValue` (or `value` + `onValueChange`) to a tab's `value`.\n * Tabs use manual activation: arrow keys move focus, Enter or Space selects.\n */\nexport function Tabs({ className, ...props }: TabsProps) {\n  return (\n    <BaseTabs.Root\n      className={cx(\"flex flex-col gap-4 data-[orientation=vertical]:flex-row data-[orientation=vertical]:gap-6\", className)}\n      {...props}\n    />\n  );\n}\n\nexport interface TabsListProps extends Omit<BaseTabs.List.Props, \"className\"> {\n  className?: string;\n}\n\n/**\n * The row of tabs on a hairline. An underline follows the active tab: it slides with a spring\n * when a tab is clicked, and jumps without animating when the selection changes from the keyboard.\n */\nexport function TabsList({ className, children, onPointerDown, onKeyDown, ...props }: TabsListProps) {\n  const [input, setInput] = useState<\"pointer\" | \"keyboard\">(\"pointer\");\n\n  return (\n    <BaseTabs.List\n      data-input={input}\n      onPointerDown={(event: PointerEvent<HTMLDivElement>) => {\n        setInput(\"pointer\");\n        onPointerDown?.(event as never);\n      }}\n      onKeyDown={(event: KeyboardEvent<HTMLDivElement>) => {\n        setInput(\"keyboard\");\n        onKeyDown?.(event as never);\n      }}\n      className={cx(\n        \"group/tabs relative z-0 flex gap-1 border-zinc-950/10 dark:border-white/10\",\n        \"data-[orientation=horizontal]:border-b data-[orientation=horizontal]:pb-1.5\",\n        \"data-[orientation=vertical]:flex-col data-[orientation=vertical]:border-l data-[orientation=vertical]:pl-1.5\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      <BaseTabs.Indicator\n        className={cx(\n          \"absolute -z-10 rounded-full bg-zinc-950 dark:bg-white\",\n          \"transition-none group-data-[input=pointer]/tabs:transition-[translate,width,height] group-data-[input=pointer]/tabs:duration-350\",\n          spring,\n          \"motion-reduce:transition-none group-data-[input=pointer]/tabs:motion-reduce:transition-none\",\n          // Horizontal: a 2px bar on the hairline under the active tab.\n          \"data-[orientation=horizontal]:-bottom-px data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:h-0.5\",\n          \"data-[orientation=horizontal]:w-(--active-tab-width) data-[orientation=horizontal]:translate-x-(--active-tab-left)\",\n          // Vertical: a 2px bar on the hairline beside the active tab.\n          \"data-[orientation=vertical]:top-0 data-[orientation=vertical]:-left-px data-[orientation=vertical]:w-0.5\",\n          \"data-[orientation=vertical]:h-(--active-tab-height) data-[orientation=vertical]:translate-y-(--active-tab-top)\",\n        )}\n      />\n    </BaseTabs.List>\n  );\n}\n\nexport interface TabsTabProps extends Omit<BaseTabs.Tab.Props, \"className\"> {\n  className?: string;\n}\n\n/** A single tab. Its `value` must match a `TabsPanel`'s `value`. */\nexport function TabsTab({ className, ...props }: TabsTabProps) {\n  return (\n    <BaseTabs.Tab\n      className={cx(\n        \"relative inline-flex h-8 cursor-pointer items-center justify-center gap-2 rounded-md px-2.5 text-sm font-medium whitespace-nowrap select-none\",\n        \"text-zinc-500 transition-[background-color,color] duration-150 ease-out motion-reduce:transition-none\",\n        \"hover:bg-zinc-950/[0.04] hover:text-zinc-950 data-active:text-zinc-950\",\n        \"dark:text-zinc-400 dark:hover:bg-white/[0.06] dark:hover:text-white dark:data-active:text-white\",\n        \"focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-sky-500\",\n        \"data-disabled:pointer-events-none data-disabled:opacity-45\",\n        \"data-[orientation=vertical]:justify-start [&_svg]:size-4 [&_svg]:shrink-0\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport interface TabsPanelProps extends Omit<BaseTabs.Panel.Props, \"className\"> {\n  className?: string;\n}\n\n/** Content shown while the tab with the same `value` is active. Fades in quickly when shown. */\nexport function TabsPanel({ className, ...props }: TabsPanelProps) {\n  return (\n    <BaseTabs.Panel\n      className={cx(\n        \"rounded-lg text-sm leading-relaxed text-zinc-600 dark:text-zinc-400\",\n        \"transition-opacity duration-150 ease-out starting:opacity-0 motion-reduce:transition-none\",\n        \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-500\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n"
    }
  ],
  "example": {
    "content": "import { Tabs, TabsList, TabsPanel, TabsTab } from \"@/components/seamless/ui/tabs\";\n\nexport default function TabsExample() {\n  return (\n    <Tabs defaultValue=\"overview\" className=\"w-full max-w-md\">\n      <TabsList aria-label=\"Project\">\n        <TabsTab value=\"overview\">Overview</TabsTab>\n        <TabsTab value=\"activity\">Activity</TabsTab>\n        <TabsTab value=\"settings\">Settings</TabsTab>\n      </TabsList>\n      <TabsPanel value=\"overview\">Three deploys this week, all healthy.</TabsPanel>\n      <TabsPanel value=\"activity\">Ada merged “Add rate limits” two hours ago.</TabsPanel>\n      <TabsPanel value=\"settings\">Builds run on every push to main.</TabsPanel>\n    </Tabs>\n  );\n}\n"
  }
}
