{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button",
  "type": "registry:ui",
  "title": "Button",
  "description": "The action button. Labels wrap to two lines rather than overflowing, so long translated text stays inside the pill.",
  "categories": [
    "actions"
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "registryDependencies": [
    "https://whiskeyjack.net/r/utils.json"
  ],
  "files": [
    {
      "path": "components/ui/button.tsx",
      "type": "registry:ui",
      "target": "components/ui/button.tsx",
      "content": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n  [\n    // Labels wrap to two lines (no whitespace-nowrap) and the sizes below use\n    // min-h, not a fixed height, so a long label -- common in translated UIs --\n    // breaks inside the button instead of overflowing it. text-center keeps a\n    // wrapped label centered.\n    \"inline-flex items-center justify-center gap-2 text-center\",\n    \"text-sm font-medium transition-all duration-200\",\n    \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2\",\n    \"disabled:pointer-events-none disabled:opacity-50\",\n    \"[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n  ],\n  {\n    variants: {\n      variant: {\n        default: [\n          \"bg-[var(--color-neutral-900)] text-white\",\n          \"hover:bg-[var(--color-neutral-800)]\",\n          \"focus-visible:ring-[var(--color-neutral-500)]\",\n          \"dark:bg-white dark:text-[var(--color-neutral-900)]\",\n          \"dark:hover:bg-[var(--color-neutral-100)]\",\n        ],\n        secondary: [\n          \"bg-[var(--color-neutral-100)] text-[var(--color-neutral-900)]\",\n          \"hover:bg-[var(--color-neutral-200)]\",\n          // Was neutral-800, which is exactly surface-dark -- so a secondary\n          // button inside a Card had no fill and no border in dark mode, and\n          // read as bare text.\n          \"dark:bg-[var(--color-surface-muted-dark)] dark:text-[var(--color-neutral-50)]\",\n          \"dark:hover:bg-[var(--color-neutral-700)]\",\n        ],\n        outline: [\n          \"border border-[var(--color-border-light)] bg-transparent\",\n          \"hover:bg-[var(--color-neutral-100)]\",\n          \"dark:border-[var(--color-border-dark)]\",\n          // The border kept it visible; the hover did not exist on a Card.\n          \"dark:hover:bg-[var(--color-surface-muted-dark)]\",\n        ],\n        ghost: [\n          \"hover:bg-[var(--color-neutral-100)]\",\n          // No border to fall back on, so on a Card this was a button with no\n          // hover feedback at all in dark mode.\n          \"dark:hover:bg-[var(--color-surface-muted-dark)]\",\n        ],\n        destructive: [\n          \"bg-[var(--color-error-500)] text-white\",\n          \"hover:bg-[var(--color-error-700)]\",\n          \"focus-visible:ring-[var(--color-error-500)]\",\n        ],\n        accent: [\n          \"bg-[var(--color-accent-500)] text-[var(--color-accent-foreground)]\",\n          \"hover:bg-[var(--color-accent-400)]\",\n          \"active:bg-[var(--color-accent-600)]\",\n          \"focus-visible:ring-[var(--color-accent-500)]\",\n        ],\n        link: [\n          \"text-[var(--color-neutral-900)] underline-offset-4\",\n          \"hover:underline\",\n          \"dark:text-white\",\n        ],\n      },\n      size: {\n        // min-h (not h) so the button grows to fit a wrapped two-line label;\n        // icon is a fixed square (no text, never wraps).\n        sm: \"min-h-8 rounded-full px-3 py-1 text-xs\",\n        default: \"min-h-10 rounded-full px-6 py-2\",\n        lg: \"min-h-12 rounded-full px-8 py-2 text-base\",\n        icon: \"h-10 w-10 rounded-full\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n);\n\nexport interface ButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {\n  /**\n   * Render the button styling onto the single child element instead of a\n   * `<button>` -- the way to make link-buttons without nesting interactive\n   * elements: `<Button asChild><Link to=\"/x\">Go</Link></Button>`. The child's\n   * own props win on conflict; classNames merge.\n   */\n  asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, asChild = false, children, ...props }, ref) => {\n    const classes = cn(buttonVariants({ variant, size, className }));\n    if (asChild) {\n      // Minimal slot (no Radix dependency): clone the single child with the\n      // button classes and remaining props merged in.\n      const child = React.Children.only(children) as React.ReactElement<\n        Record<string, unknown>\n      >;\n      const mergedProps: Record<string, unknown> = {\n        ...props,\n        ...child.props,\n        className: cn(classes, child.props.className as string | undefined),\n        ref,\n      };\n      return React.cloneElement(child, mergedProps);\n    }\n    return (\n      <button className={classes} ref={ref} {...props}>\n        {children}\n      </button>\n    );\n  }\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n"
    }
  ],
  "docs": "Use the default size (40px) for every action button in an app; it matches size=\"icon\" so text and icon buttons align. Reserve size=\"lg\" for hero and landing CTAs. Avoid size=\"sm\", which breaks rhythm against neighbouring default buttons. The accent variant is the primary action, outline the secondary one.",
  "meta": {
    "group": "actions",
    "related": [
      "button-row",
      "confirm-button",
      "drawer-action"
    ],
    "exports": [
      "Button"
    ],
    "siteSlug": "button"
  }
}
