{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "tabs",
  "title": "Tabs",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/tabs/Tabs.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n  import type { TabsOrientation } from './context'\n\n  export interface TabsProps extends HTMLAttributes<HTMLDivElement> {\n    /** Controlled active value. Bind with `bind:value`. */\n    value?: string\n    /** Initial active value for uncontrolled usage. */\n    defaultValue?: string\n    orientation?: TabsOrientation\n    ref?: HTMLDivElement | null\n    onValueChange?: (value: string) => void\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { nextTabsId, setTabsContext } from './context'\n\n  let {\n    class: className,\n    defaultValue,\n    value = $bindable(defaultValue),\n    orientation = 'horizontal',\n    children,\n    ref = $bindable(null),\n    onValueChange,\n    ...restProps\n  }: TabsProps = $props()\n\n  const baseId = nextTabsId()\n\n  function select(next: string) {\n    value = next\n    onValueChange?.(next)\n  }\n\n  setTabsContext({\n    current: () => value,\n    orientation: () => orientation,\n    select,\n    triggerId: (v: string) => `${baseId}-trigger-${v}`,\n    contentId: (v: string) => `${baseId}-content-${v}`,\n  })\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge=\"\"\n  data-slot=\"tabs\"\n  data-orientation={orientation}\n  class={cn('flex w-full', orientation === 'vertical' ? 'flex-row gap-4' : 'flex-col gap-2', className)}\n  {...restProps}\n>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/tabs/Tabs.svelte"
    },
    {
      "path": "packages/registry-svelte/components/tabs/TabsContent.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface TabsContentProps extends HTMLAttributes<HTMLDivElement> {\n    value: string\n    forceMount?: boolean\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { getTabsContext } from './context'\n\n  let { class: className, value, forceMount = false, children, ref = $bindable(null), ...restProps }: TabsContentProps =\n    $props()\n\n  const ctx = getTabsContext()\n  const isActive = $derived(ctx?.current() === value)\n</script>\n\n{#if forceMount || isActive}\n  <div\n    bind:this={ref}\n    role=\"tabpanel\"\n    id={ctx?.contentId(value)}\n    aria-labelledby={ctx?.triggerId(value)}\n    tabindex={0}\n    hidden={!isActive || undefined}\n    data-uipkge=\"\"\n    data-slot=\"tabs-content\"\n    data-state={isActive ? 'active' : 'inactive'}\n    class={cn(\n      'ring-offset-background focus-visible:border-ring focus-visible:ring-ring/50 motion-safe:data-[state=active]:animate-in motion-safe:data-[state=active]:fade-in-0 motion-safe:data-[state=active]:blur-in-2 motion-safe:data-[state=active]:slide-in-from-bottom-1 motion-safe:data-[state=active]:ease-emphasized flex-1 focus-visible:ring-2 focus-visible:ring-[3px] focus-visible:outline-none motion-safe:data-[state=active]:duration-200',\n      className,\n    )}\n    {...restProps}\n  >\n    {@render children?.()}\n  </div>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/tabs/TabsContent.svelte"
    },
    {
      "path": "packages/registry-svelte/components/tabs/TabsList.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n  import type { TabsOrientation } from './context'\n  import type { TabsListVariants } from './tabs.variants'\n\n  export interface TabsListProps extends HTMLAttributes<HTMLDivElement> {\n    variant?: TabsListVariants['variant']\n    orientation?: TabsOrientation\n    /** Enable sliding active indicator (default true). When false, active surface paints on the trigger. */\n    animated?: boolean\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import type { KeyboardEventHandler } from 'svelte/elements'\n  import { cn } from '$lib/utils'\n  import { getTabsContext } from './context'\n  import { tabsListVariants } from './tabs.variants'\n\n  let {\n    class: className,\n    variant,\n    orientation,\n    animated = true,\n    children,\n    ref = $bindable(null),\n    onkeydown,\n    ...restProps\n  }: TabsListProps = $props()\n\n  const ctx = getTabsContext()\n  // Inherit orientation from <Tabs> when not set explicitly.\n  const effectiveOrientation = $derived(orientation ?? ctx?.orientation() ?? 'horizontal')\n  const effectiveVariant = $derived(variant ?? 'segmented')\n\n  let listEl = $state<HTMLDivElement | null>(null)\n  let indicatorStyle = $state<Record<string, string>>({ opacity: '0' })\n  let firstPosition = true\n\n  $effect(() => {\n    ref = listEl\n  })\n\n  function motionSafeTransition(): string {\n    if (typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {\n      return 'none'\n    }\n    return firstPosition\n      ? 'none'\n      : 'transform 220ms cubic-bezier(0.22, 1, 0.36, 1), width 220ms cubic-bezier(0.22, 1, 0.36, 1), height 220ms cubic-bezier(0.22, 1, 0.36, 1)'\n  }\n\n  function updateIndicator() {\n    const root = listEl ?? ref\n    if (!root || typeof window === 'undefined') return\n    const active = root.querySelector<HTMLElement>('[data-slot=\"tabs-trigger\"][data-state=\"active\"]')\n    if (!active) {\n      indicatorStyle = { opacity: '0' }\n      return\n    }\n    const listRect = root.getBoundingClientRect()\n    const activeRect = active.getBoundingClientRect()\n    const left = activeRect.left - listRect.left + root.scrollLeft\n    const top = activeRect.top - listRect.top + root.scrollTop\n    const transition = motionSafeTransition()\n    if (effectiveVariant === 'underline') {\n      const thickness = 2\n      if (effectiveOrientation === 'vertical') {\n        indicatorStyle = {\n          width: `${thickness}px`,\n          height: `${activeRect.height}px`,\n          transform: `translate3d(${listRect.width - thickness}px, ${top}px, 0)`,\n          opacity: '1',\n          transition,\n        }\n      } else {\n        indicatorStyle = {\n          width: `${activeRect.width}px`,\n          height: `${thickness}px`,\n          transform: `translate3d(${left}px, ${listRect.height - thickness}px, 0)`,\n          opacity: '1',\n          transition,\n        }\n      }\n    } else {\n      indicatorStyle = {\n        width: `${activeRect.width}px`,\n        height: `${activeRect.height}px`,\n        transform: `translate3d(${left}px, ${top}px, 0)`,\n        opacity: '1',\n        transition,\n      }\n    }\n    firstPosition = false\n  }\n\n  // Observe size + active-trigger changes while animated. Re-binds when the\n  // variant, orientation, or animated flag changes (first paint has no slide).\n  $effect(() => {\n    if (!animated) {\n      indicatorStyle = { opacity: '0' }\n      return\n    }\n    // Tracked reads: re-run the effect when these change.\n    void effectiveVariant\n    void effectiveOrientation\n    void ctx?.current()\n    const root = listEl ?? ref\n    if (!root || typeof window === 'undefined') return\n    firstPosition = true\n    // Defer past layout so getBoundingClientRect reads settled geometry.\n    const raf = requestAnimationFrame(() => updateIndicator())\n    const ro = new ResizeObserver(() => updateIndicator())\n    ro.observe(root)\n    root.querySelectorAll('[data-slot=\"tabs-trigger\"]').forEach((el) => ro.observe(el))\n    const mo = new MutationObserver((mutations) => {\n      for (const m of mutations) {\n        if (m.type === 'childList') root.querySelectorAll('[data-slot=\"tabs-trigger\"]').forEach((el) => ro.observe(el))\n      }\n      updateIndicator()\n    })\n    mo.observe(root, { attributes: true, attributeFilter: ['data-state'], subtree: true, childList: true })\n    return () => {\n      cancelAnimationFrame(raf)\n      ro.disconnect()\n      mo.disconnect()\n    }\n  })\n\n  const indicatorClass = $derived(\n    effectiveVariant === 'pill'\n      ? 'pointer-events-none absolute top-0 left-0 z-0 rounded-full bg-primary shadow-xs will-change-transform'\n      : effectiveVariant === 'underline'\n        ? 'pointer-events-none absolute top-0 left-0 z-0 bg-foreground will-change-transform'\n        : 'pointer-events-none absolute top-0 left-0 z-0 rounded-sm bg-background shadow-xs will-change-transform',\n  )\n\n  function indicatorStyleAttr(): string {\n    return Object.entries(indicatorStyle)\n      .map(([k, v]) => `${k}: ${v}`)\n      .join('; ')\n  }\n\n  // Roving keyboard navigation with automatic activation (matches reka-ui\n  // Tabs defaults): arrows move + select, Home/End jump.\n  const handleKeydown: KeyboardEventHandler<HTMLDivElement> = (event) => {\n    onkeydown?.(event)\n    if (event.defaultPrevented) return\n    const root = listEl ?? ref\n    if (!root) return\n    const vertical = effectiveOrientation === 'vertical'\n    const nextKeys = vertical ? ['ArrowDown', 'ArrowRight'] : ['ArrowRight', 'ArrowDown']\n    const prevKeys = vertical ? ['ArrowUp', 'ArrowLeft'] : ['ArrowLeft', 'ArrowUp']\n    if (![...nextKeys, ...prevKeys, 'Home', 'End'].includes(event.key)) return\n    event.preventDefault()\n    const triggers = [...root.querySelectorAll<HTMLElement>('[data-slot=\"tabs-trigger\"]:not([disabled])')]\n    if (triggers.length === 0) return\n    const current = triggers.indexOf(document.activeElement as HTMLElement)\n    let next: number\n    if (event.key === 'Home') next = 0\n    else if (event.key === 'End') next = triggers.length - 1\n    else if (nextKeys.includes(event.key)) next = (current + 1 + triggers.length) % triggers.length\n    else next = (current - 1 + triggers.length) % triggers.length\n    triggers[next]?.focus()\n    triggers[next]?.click()\n  }\n\n</script>\n\n<div\n  bind:this={listEl}\n  role=\"tablist\"\n  aria-orientation={effectiveOrientation}\n  data-uipkge=\"\"\n  data-slot=\"tabs-list\"\n  data-animated={animated ? 'true' : 'false'}\n  class={cn(\n    'group/list relative',\n    tabsListVariants({ variant: effectiveVariant, orientation: effectiveOrientation }),\n    className,\n  )}\n  onkeydown={handleKeydown}\n  {...restProps}\n>\n  {#if animated}\n    <span data-slot=\"tabs-indicator\" aria-hidden=\"true\" class={indicatorClass} style={indicatorStyleAttr()}></span>\n  {/if}\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/tabs/TabsList.svelte"
    },
    {
      "path": "packages/registry-svelte/components/tabs/TabsTrigger.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLButtonAttributes } from 'svelte/elements'\n  import type { TabsOrientation } from './context'\n  import type { TabsTriggerVariants } from './tabs.variants'\n\n  export interface TabsTriggerProps extends HTMLButtonAttributes {\n    value: string\n    size?: TabsTriggerVariants['size']\n    variant?: TabsTriggerVariants['variant']\n    orientation?: TabsOrientation\n    ref?: HTMLButtonElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { getTabsContext } from './context'\n  import { tabsTriggerVariants } from './tabs.variants'\n\n  let {\n    class: className,\n    value,\n    size,\n    variant,\n    orientation,\n    disabled = false,\n    children,\n    ref = $bindable(null),\n    onclick,\n    ...restProps\n  }: TabsTriggerProps = $props()\n\n  const ctx = getTabsContext()\n  const effectiveOrientation = $derived(orientation ?? ctx?.orientation() ?? 'horizontal')\n  const isActive = $derived(ctx?.current() === value)\n</script>\n\n<button\n  bind:this={ref}\n  type=\"button\"\n  role=\"tab\"\n  id={ctx?.triggerId(value)}\n  aria-selected={isActive}\n  aria-controls={ctx?.contentId(value)}\n  tabindex={isActive ? 0 : -1}\n  data-uipkge=\"\"\n  data-slot=\"tabs-trigger\"\n  data-state={isActive ? 'active' : 'inactive'}\n  disabled={disabled}\n  class={cn(\n    tabsTriggerVariants({ size, variant, orientation: effectiveOrientation }),\n    className,\n  )}\n  onclick={(event) => {\n    onclick?.(event)\n    if (!event.defaultPrevented && !disabled) ctx?.select(value)\n  }}\n  {...restProps}\n>\n  {@render children?.()}\n</button>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/tabs/TabsTrigger.svelte"
    },
    {
      "path": "packages/registry-svelte/components/tabs/tabs.variants.ts",
      "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\n/**\n * Variant definitions live in their own file (rather than the package\n * `index.ts`) so consuming Vue SFCs can import without creating a circular\n * dependency through the index. See card.variants.ts for the canonical\n * example + the SSR symptom that motivated the split.\n */\n\nexport const tabsListVariants = cva(\n  // Scrollable when triggers overflow. Centering uses trigger auto margins\n  // instead of justify-center so an overflowing list stays scrollable from its\n  // start edge (justify-center + overflow makes leading items unreachable).\n  // The scrollbar is hidden; scrolling still works via wheel/touch/keyboard.\n  'inline-flex h-9 max-w-full items-center overflow-x-auto rounded-lg bg-muted p-1 text-muted-foreground overscroll-x-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden [&>[data-slot=tabs-trigger]:first-child]:ml-auto [&>[data-slot=tabs-trigger]:last-child]:mr-auto',\n  {\n    variants: {\n      variant: {\n        // Solid muted track with rounded inset triggers — the default look.\n        segmented: 'gap-1 rounded-lg bg-muted p-1',\n        // Transparent track with rounded-full pill triggers.\n        pill: 'h-auto gap-2 bg-transparent p-0',\n        // Bottom-border bar (horizontal) or right-border bar (vertical), with\n        // an underline on the active trigger.\n        underline: 'h-auto gap-0 bg-transparent p-0',\n      },\n      orientation: {\n        horizontal: 'flex-row',\n        vertical: 'h-auto flex-col items-stretch',\n      },\n    },\n    compoundVariants: [\n      {\n        variant: 'underline',\n        orientation: 'horizontal',\n        // Neutralize the base auto-margin centering; this variant aligns start.\n        class:\n          'w-full justify-start border-b border-border [&>[data-slot=tabs-trigger]:first-child]:ml-0 [&>[data-slot=tabs-trigger]:last-child]:mr-0',\n      },\n      {\n        variant: 'underline',\n        orientation: 'vertical',\n        class: 'border-r border-border',\n      },\n    ],\n    defaultVariants: {\n      variant: 'segmented',\n      orientation: 'horizontal',\n    },\n  },\n)\n\nexport const tabsTriggerVariants = cva(\n  // z-10 keeps label above the sliding indicator; active surface paints on the indicator\n  // when the parent list has data-animated=\"true\". Static active chrome restores when\n  // data-animated=\"false\" (see group-data variants below).\n  'relative z-10 inline-flex shrink-0 items-center justify-center gap-1.5 whitespace-nowrap font-medium ring-offset-background [&_svg]:shrink-0 transition-colors duration-200 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',\n  {\n    variants: {\n      variant: {\n        // Active bg/shadow live on TabsList indicator so it can slide between triggers.\n        segmented:\n          'rounded-md data-[state=active]:text-foreground group-data-[animated=false]/list:data-[state=active]:bg-background group-data-[animated=false]/list:data-[state=active]:shadow-xs',\n        pill: 'rounded-full border border-border bg-transparent data-[state=active]:border-transparent data-[state=active]:text-primary-foreground group-data-[animated=false]/list:data-[state=active]:bg-primary group-data-[animated=false]/list:data-[state=active]:border-primary',\n        // Underline bar slides on TabsList indicator; keep transparent border for layout stability.\n        underline:\n          'rounded-none border-b-2 border-transparent -mb-px data-[state=active]:text-foreground group-data-[animated=false]/list:data-[state=active]:border-foreground',\n      },\n      size: {\n        default: 'h-7 px-3 text-sm',\n        sm: 'h-6 px-2.5 text-xs',\n        lg: 'h-8 px-4 text-sm',\n      },\n      orientation: {\n        horizontal: '',\n        vertical: 'w-full justify-start',\n      },\n    },\n    compoundVariants: [\n      {\n        variant: 'underline',\n        orientation: 'vertical',\n        class: 'border-b-0 border-r-2 -mr-px group-data-[animated=false]/list:data-[state=active]:border-foreground',\n      },\n    ],\n    defaultVariants: {\n      variant: 'segmented',\n      size: 'default',\n      orientation: 'horizontal',\n    },\n  },\n)\n\nexport type TabsListVariants = VariantProps<typeof tabsListVariants>\n\nexport type TabsTriggerVariants = VariantProps<typeof tabsTriggerVariants>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/tabs/tabs.variants.ts"
    },
    {
      "path": "packages/registry-svelte/components/tabs/context.ts",
      "content": "import { getContext, setContext } from 'svelte'\n\nexport type TabsOrientation = 'horizontal' | 'vertical'\n\n/**\n * Tabs state shared from <Tabs> to its descendants. Exposed as functions so\n * reads inside child templates/`$derived` track the parent's runes state.\n */\nexport interface TabsContextValue {\n  current: () => string | undefined\n  orientation: () => TabsOrientation\n  select: (value: string) => void\n  triggerId: (value: string) => string\n  contentId: (value: string) => string\n}\n\nconst KEY = Symbol.for('uipkge:tabs')\n\nexport function setTabsContext(value: TabsContextValue): TabsContextValue {\n  setContext(KEY, value)\n  return value\n}\n\nexport function getTabsContext(): TabsContextValue | null {\n  return getContext<TabsContextValue | null>(KEY) ?? null\n}\n\nlet uid = 0\n\n/** Instance-unique id prefix for trigger/content aria linking. */\nexport function nextTabsId(): string {\n  uid += 1\n  return `uipkge-tabs-${uid}`\n}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/tabs/context.ts"
    },
    {
      "path": "packages/registry-svelte/components/tabs/index.ts",
      "content": "export { default as Tabs, type TabsProps } from './Tabs.svelte'\nexport { default as TabsContent, type TabsContentProps } from './TabsContent.svelte'\nexport { default as TabsList, type TabsListProps } from './TabsList.svelte'\nexport { default as TabsTrigger, type TabsTriggerProps } from './TabsTrigger.svelte'\nexport { tabsListVariants, tabsTriggerVariants, type TabsListVariants, type TabsTriggerVariants } from './tabs.variants'\nexport type { TabsOrientation } from './context'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/tabs/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Horizontal tab navigation with content panels — pick one panel at a time. Underline or pills variants. Hand-rolled roving keyboard navigation with automatic activation.",
  "categories": [
    "navigation"
  ]
}