{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toggle-group",
  "type": "registry:ui",
  "title": "Toggle Group",
  "description": "An accessible React toggle group for Next.js: buttons you switch on and off independently, like Bold, Italic and Underline in a toolbar.",
  "author": "FewTone <https://fewtone.com>",
  "dependencies": [
    "class-variance-authority@^0.7.1",
    "motion@^14.0.0",
    "radix-ui@^1.7.0"
  ],
  "registryDependencies": [
    "https://ui.fewtone.com/r/tokens.json",
    "https://ui.fewtone.com/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/fewtone/ui/toggle-group.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { duration, ease, spring } from \"@/lib/motion\";\nimport { cn } from \"@/lib/utils\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { ToggleGroup as ToggleGroupPrimitive } from \"radix-ui\";\nimport {\n  type ComponentProps,\n  createContext,\n  type Ref,\n  useContext,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\";\n\n/*\n * Toggle Group: buttons you switch on and off independently, like Bold / Italic / Underline (owner,\n * 2026-10-14: split from Switcher, which is for one choice; Lab only until it's approved, so no meta yet).\n * - Each pressed option fades its own pill in (opacity, plus a small scale on spring.snappy) and out quickly.\n * - A toolbar of aria-pressed buttons (Radix Toggle Group, multiple). Arrow keys move between options; Home\n *   and End jump to the ends.\n * - Flat. The pill takes the page's own colour, dark in dark mode and light in light mode (owner,\n *   2026-10-14). Default: a muted track. Outline: a bordered track, and the pill gets a hairline too, so it\n *   shows on any surface. No shadows and no\n *   scale on the buttons. Inactive options are muted and brighten on hover.\n * - The pill's corners follow the option's own radius (read from it).\n */\n\nconst toggleGroupVariants = cva(\"relative isolate inline-flex w-fit items-center gap-0.5 rounded-lg p-0.5\", {\n  variants: {\n    variant: {\n      default: \"bg-muted\",\n      outline: \"border border-input\",\n    },\n  },\n  defaultVariants: { variant: \"default\" },\n});\n\n// The options sit 2px inside the track, so the track is a control height (32, 36 or 40px)\nconst toggleGroupItemVariants = cva(\n  [\n    \"relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-1.5 rounded-md text-sm font-medium whitespace-nowrap\",\n    \"text-muted-foreground transition-colors duration-(--duration-base) ease-out data-[state=off]:hover:text-foreground data-[state=on]:text-foreground\",\n    \"outline-ring focus-visible:outline-(length:--focus-ring-width) focus-visible:outline-offset-(--focus-ring-offset)\",\n    \"disabled:pointer-events-none disabled:opacity-(--opacity-disabled)\",\n    \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-(--size-icon-md)\",\n  ],\n  {\n    variants: {\n      size: {\n        sm: \"h-7 px-2.5\",\n        md: \"h-8 px-3\",\n        lg: \"h-9 px-4\",\n      },\n    },\n    defaultVariants: { size: \"md\" },\n  },\n);\n\nconst GroupContext = createContext<{\n  value: string[];\n  variant: \"default\" | \"outline\";\n  size: \"sm\" | \"md\" | \"lg\";\n  reduce: boolean;\n} | null>(null);\n\ntype RootProps = Omit<\n  Extract<ComponentProps<typeof ToggleGroupPrimitive.Root>, { type: \"multiple\" }>,\n  \"type\" | \"value\" | \"defaultValue\" | \"onValueChange\"\n>;\n\nexport type ToggleGroupProps = RootProps &\n  VariantProps<typeof toggleGroupVariants> &\n  VariantProps<typeof toggleGroupItemVariants> & {\n    /** The options switched on, when you control them */\n    value?: string[];\n    /** The options switched on at first */\n    defaultValue?: string[];\n    /** Called with every option that's on, after each change */\n    onValueChange?: (value: string[]) => void;\n  };\n\nfunction ToggleGroup({\n  value,\n  defaultValue,\n  onValueChange,\n  variant,\n  size,\n  className,\n  ...props\n}: ToggleGroupProps) {\n  const reduce = useReducedMotion() ?? false;\n  // Mirror the value, controlled or not, so options know whether they're on\n  const [internal, setInternal] = useState<string[]>(defaultValue ?? []);\n  const current = value ?? internal;\n\n  return (\n    <GroupContext.Provider\n      value={{ value: current, variant: variant ?? \"default\", size: size ?? \"md\", reduce }}\n    >\n      <ToggleGroupPrimitive.Root\n        data-slot=\"toggle-group\"\n        data-variant={variant ?? \"default\"}\n        type=\"multiple\"\n        value={current}\n        onValueChange={(next) => {\n          setInternal(next);\n          onValueChange?.(next);\n        }}\n        className={cn(toggleGroupVariants({ variant }), className)}\n        {...props}\n      />\n    </GroupContext.Provider>\n  );\n}\n\n/** Hands the option's node to our ref and to the caller's */\nfunction useMergedRef<T>(own: { current: T | null }, theirs?: Ref<T>) {\n  return (node: T | null) => {\n    own.current = node;\n    if (typeof theirs === \"function\") theirs(node);\n    else if (theirs) (theirs as { current: T | null }).current = node;\n  };\n}\n\nfunction ToggleGroupItem({\n  className,\n  children,\n  value,\n  ref,\n  ...props\n}: ComponentProps<typeof ToggleGroupPrimitive.Item>) {\n  const group = useContext(GroupContext);\n  const node = useRef<HTMLButtonElement>(null);\n  const setRef = useMergedRef(node, ref);\n  const [radius, setRadius] = useState(0);\n\n  // The pill takes the option's corner radius, whatever the user's --radius is\n  useLayoutEffect(() => {\n    if (node.current) setRadius(Number.parseFloat(getComputedStyle(node.current).borderTopLeftRadius) || 0);\n  }, []);\n\n  const on = group?.value.includes(value) ?? false;\n  const reduce = group?.reduce ?? false;\n\n  return (\n    <ToggleGroupPrimitive.Item\n      ref={setRef}\n      data-slot=\"toggle-group-item\"\n      value={value}\n      className={cn(toggleGroupItemVariants({ size: group?.size }), className)}\n      {...props}\n    >\n      <AnimatePresence initial={false}>\n        {on ? (\n          <motion.span\n            key=\"pill\"\n            aria-hidden\n            className={cn(\n              \"absolute inset-0 -z-10\",\n              group?.variant === \"outline\" ? \"border border-input bg-background\" : \"bg-background\",\n            )}\n            style={{ borderRadius: radius }}\n            initial={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.92 }}\n            animate={{ opacity: 1, scale: 1 }}\n            exit={\n              reduce\n                ? { opacity: 0 }\n                : { opacity: 0, scale: 0.92, transition: { duration: duration.fast, ease: ease.in } }\n            }\n            transition={reduce ? { duration: duration.fast } : spring.snappy}\n          />\n        ) : null}\n      </AnimatePresence>\n      {children}\n    </ToggleGroupPrimitive.Item>\n  );\n}\n\nexport { ToggleGroup, ToggleGroupItem, toggleGroupItemVariants, toggleGroupVariants };\n"
    }
  ],
  "categories": [
    "buttons"
  ],
  "docs": "Usage and examples: https://ui.fewtone.com/components/toggle-group"
}
