{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "floating-nav",
  "title": "Floating Nav",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A pill-shaped navigation that floats over the page, with an indicator that glides between items on a spring and optional hide-on-scroll.",
  "dependencies": [
    "motion@^12"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/scroll.json",
    "https://ui.ballmac.com/r/motion-presets.json",
    "https://ui.ballmac.com/r/i18n.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/floating-nav.tsx",
      "content": "// Ballmac UI: Floating Nav. https://ui.ballmac.com/components/floating-nav\n\"use client\";\n\nimport * as React from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { useScrollDirection, useScrolled, type ScrollContainer } from \"@/lib/ballmac/scroll\";\nimport { spring } from \"@/lib/ballmac/motion\";\nimport { cn } from \"@/lib/utils\";\nimport { useMessages } from \"@/lib/ballmac/i18n\";\n\ntype FloatingNavItem = {\n  /** Unique value; used for `value` and `onValueChange`. */\n  value: string;\n  /** Visible label (hidden on small screens when an icon is given, but still the accessible name). */\n  label: string;\n  /** Link destination. */\n  href?: string;\n  /** Icon shown before the label. */\n  icon?: React.ReactNode;\n};\n\ntype FloatingNavProps = Omit<React.ComponentProps<\"nav\">, \"onChange\" | \"defaultValue\"> & {\n  /** Links in the bar. */\n  items: FloatingNavItem[];\n  /** Controlled active item. */\n  value?: string;\n  /** Initial active item when uncontrolled. */\n  defaultValue?: string;\n  /** Called when an item is chosen. */\n  onValueChange?: (value: string) => void;\n  /** Edge of the screen the bar floats from. */\n  position?: \"top\" | \"bottom\";\n  /** Slide away when scrolling down (top) or up (bottom), and return on the opposite scroll. Stays while it holds focus. */\n  autoHide?: boolean;\n  /** Accessible name of the navigation landmark. */\n  label?: string;\n  /** A scrollable element to watch for auto-hide instead of the page. */\n  scrollContainer?: ScrollContainer;\n};\n\n/**\n * A pill-shaped navigation that floats over the page, with an active indicator that glides between items on a spring.\n * Positioned with `fixed`; add `absolute` in `className` to place it inside a positioned container.\n */\nfunction FloatingNav({\n  items,\n  value: valueProp,\n  defaultValue,\n  onValueChange,\n  position = \"top\",\n  autoHide = false,\n  label,\n  scrollContainer,\n  className,\n  ...props\n}: FloatingNavProps) {\n  const msg = useMessages()\n  label ??= msg(\"floating-nav.label\", \"Primary\")\n  const reduce = useReducedMotion();\n  const indicatorId = `floating-nav-${React.useId()}`;\n  const [inner, setInner] = React.useState(defaultValue ?? items[0]?.value);\n  const value = valueProp ?? inner;\n  const scrolled = useScrolled(24, scrollContainer);\n  const direction = useScrollDirection(12, scrollContainer);\n  const hidden = autoHide && scrolled && (position === \"top\" ? direction === \"down\" : direction === \"up\");\n  return (\n    <nav\n      aria-label={label}\n      data-slot=\"floating-nav\"\n      data-hidden={hidden || undefined}\n      className={cn(\n        \"fixed left-1/2 z-40 max-w-[calc(100vw-1.5rem)] -translate-x-1/2 transition-[transform,opacity] duration-300 motion-reduce:transition-none\",\n        position === \"top\" ? \"top-4\" : \"bottom-4\",\n        hidden && (position === \"top\" ? \"-translate-y-[calc(100%+1.5rem)]\" : \"translate-y-[calc(100%+1.5rem)]\"),\n        hidden && \"opacity-0 focus-within:translate-y-0 focus-within:opacity-100\",\n        className,\n      )}\n      {...props}\n    >\n      <ul className=\"flex items-center gap-0.5 rounded-full border bg-background/80 p-1 shadow-[0_8px_32px_-8px_rgb(0_0_0/0.25)] backdrop-blur-xl supports-[backdrop-filter]:bg-background/60\">\n        {items.map((item) => {\n          const active = item.value === value;\n          return (\n            <li key={item.value}>\n              <a\n                href={item.href}\n                aria-current={active ? \"page\" : undefined}\n                aria-label={item.icon ? item.label : undefined}\n                onClick={() => {\n                  if (valueProp === undefined) setInner(item.value);\n                  onValueChange?.(item.value);\n                }}\n                className={cn(\n                  \"relative inline-flex h-9 items-center gap-2 rounded-full px-3.5 text-sm font-medium outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4\",\n                  active ? \"text-primary-foreground\" : \"text-muted-foreground hover:text-foreground\",\n                )}\n              >\n                {active && (\n                  <motion.span\n                    layoutId={indicatorId}\n                    aria-hidden=\"true\"\n                    className=\"absolute inset-0 rounded-full bg-primary shadow-sm\"\n                    transition={reduce ? { duration: 0 } : spring.snappy}\n                  />\n                )}\n                <span className=\"relative flex items-center gap-2\">\n                  {item.icon}\n                  <span className={cn(item.icon && \"max-sm:sr-only\")}>{item.label}</span>\n                </span>\n              </a>\n            </li>\n          );\n        })}\n      </ul>\n    </nav>\n  );\n}\n\nexport { FloatingNav, type FloatingNavProps, type FloatingNavItem };\n",
      "type": "registry:ui",
      "target": "@components/ballmac/floating-nav.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "navigation",
      "pill",
      "floating",
      "mobile"
    ],
    "version": "1.0.0",
    "updated": "2026-09-30",
    "ai": {
      "summary": "items: {value,label,href,icon}. position top or bottom; autoHide slides it away on scroll; value/defaultValue/onValueChange pick the active item.",
      "whenToUse": [
        "Landing pages and portfolios",
        "App-style bottom navigation on small screens"
      ],
      "whenNotToUse": [
        "Sites that need nested menus; use navbar with mega-menu",
        "Long lists of links"
      ],
      "composesWith": [
        "navbar",
        "button"
      ],
      "a11y": [
        {
          "keys": "Tab",
          "action": "Moves through the links"
        },
        {
          "keys": "Screen readers",
          "action": "Active item has aria-current; icon-only items keep their label"
        },
        {
          "keys": "Reduced motion",
          "action": "The indicator jumps instead of gliding; hiding uses no transition"
        }
      ],
      "customization": [
        "position: top | bottom",
        "autoHide",
        "icons (labels collapse on small screens)",
        "scrollContainer"
      ]
    },
    "examples": [
      "floating-nav-demo",
      "floating-nav-states"
    ],
    "url": "https://ui.ballmac.com/components/floating-nav"
  },
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}