shadimage
saastro
uibutton-prosource: ok · hit

Button Pro

Enhanced button with CVA variants, icon effects, shine, gooey, underline, and gradient animations.

npx shadcn add "https://ui.saastro.io/r/button-pro.json"
free

Visual style

gradient

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
ui-primitives@base-ui/react

Files1 · source cached 10/14/2026, 10:04:14 PM

registry/default/ui/button-pro.tsx
"use client";

import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "@/lib/utils";

const buttonProVariants = cva(
  `
      inline-flex items-center justify-center gap-2 whitespace-nowrap shrink-0

      font-medium text-base

      transition-all

      disabled:pointer-events-none
      disabled:opacity-50

      [&_svg]:pointer-events-none
      [&_svg:not([class*='size-'])]:size-6
      [&_svg]:shrink-0

      outline-none
      focus-visible:border-ring
      focus-visible:ring-ring/50
      focus-visible:ring-[3px]
      aria-invalid:ring-destructive/20
      dark:aria-invalid:ring-destructive/40
      aria-invalid:border-destructive
    `,
  {
    variants: {
      variant: {
        default: `
            bg-primary
            text-primary-foreground
            hover:bg-yellow-500
          `,
        destructive: `
            bg-destructive
            text-destructive-foreground
            hover:bg-destructive/90
          `,
        outline: `
            border
            border-input
            bg-background
            hover:bg-accent
            hover:text-accent-foreground
          `,
        secondary: `
            bg-secondary
            text-secondary-foreground
            hover:bg-secondary/80
          `,
        ghost: `
            hover:bg-accent
            hover:text-accent-foreground
          `,
        link: `
            text-primary
            underline-offset-4
            hover:underline
          `,
        accent: `
            bg-accent
            text-accent-foreground
            hover:bg-accent/90
          `,
        foreground: `
            bg-foreground
            text-muted
          `,
      },
      iconfx: {
        none: "",
        left: `
          [&>svg]:w-0
          [&>svg]:translate-x-[0%]
          [&>svg]:pr-0
          [&>svg]:opacity-0
          [&>svg]:transition-all
          [&>svg]:duration-200
          group-hover:[&>svg]:w-5
          group-hover:[&>svg]:translate-x-100
          group-hover:[&>svg]:pr-2
          group-hover:[&>svg]:opacity-100
        `,
        right: `
          [&>svg]:w-0
          [&>svg]:translate-x-[100%]
          [&>svg]:pl-0
          [&>svg]:opacity-0
          [&>svg]:transition-all
          [&>svg]:duration-200
          group-hover:[&>svg]:w-5
          group-hover:[&>svg]:translate-x-0
          group-hover:[&>svg]:pl-2
          group-hover:[&>svg]:opacity-100
        `,
      },
      effect: {
        default: "",
        expandIcon: `
            group
            gap-0
            relative
          `,
        ringHover: `
            transition-all
            duration-300
            hover:ring-2
            hover:ring-primary/90
            hover:ring-offset-2
          `,
        shine: `
          relative
          overflow-hidden
          before:absolute
          before:inset-0
          before:rounded-[inherit]
          before:content-['']
          before:bg-[linear-gradient(45deg,transparent_25%,rgba(255,255,255,0.5)_50%,transparent_75%,transparent_100%)]
          before:bg-[length:250%_250%]
          before:bg-no-repeat
          before:bg-[position:200%_0]
          before:animate-[shine_3s_ease-out_infinite]
        `,

        shineHover: `
          relative
          overflow-hidden
          before:absolute
          before:inset-0
          before:rounded-[inherit]
          before:content-['']
          before:bg-[linear-gradient(45deg,transparent_25%,rgba(255,255,255,0.5)_50%,transparent_75%,transparent_100%)]
          before:bg-[length:250%_250%]
          before:bg-no-repeat
          before:bg-[position:200%_0]
          before:transition-[background-position]
          hover:before:bg-[position:-100%_0]
          before:duration-1000
        `,
        gooeyRight: `
            relative
            z-0
            overflow-hidden
            transition-all
            duration-500
            before:absolute
            before:inset-0
            before:-z-10
            before:translate-x-[150%]
            before:translate-y-[150%]
            before:scale-[2.5]
            before:rounded-[100%]
            before:bg-gradient-to-r
            before:from-white/40
            before:transition-transform
            before:duration-1000
            hover:before:translate-x-[0%]
            hover:before:translate-y-[0%]
          `,
        gooeyLeft: `
            relative z-0 overflow-hidden transition-all duration-500
            after:absolute after:inset-0 after:-z-10
            after:translate-x-[-150%] after:translate-y-[150%] after:scale-[2.5]
            after:rounded-[100%] after:bg-gradient-to-l after:from-white/40
            after:transition-transform after:duration-1000
            hover:after:translate-x-[0%] hover:after:translate-y-[0%]
        `,
        underline: `
            relative
            !no-underline
            after:absolute
            after:bg-[currentColor]
            after:bottom-2
            after:h-[1px]
            after:w-2/3
            after:origin-bottom-left
            after:scale-x-100
            hover:after:origin-bottom-right
            hover:after:scale-x-0
            after:transition-transform
            after:ease-in-out
            after:duration-300
          `,
        hoverUnderline: `
            relative
            !no-underline
            after:absolute
            after:bg-[currentColor]
            after:bottom-2
            after:h-[1px]
            after:w-2/3
            after:origin-bottom-right
            after:scale-x-0
            hover:after:origin-bottom-left
            hover:after:scale-x-100
            after:transition-transform
            after:ease-in-out
            after:duration-300
          `,
        gradientSlideShow: `
            bg-[size:400%]
            bg-linear-to-r/longer from-indigo-500 to-teal-400
            animate-gradient-flow
          `,
      },
      rounded: {
        xs: "rounded-xs",
        sm: "rounded-sm",
        md: "rounded-md",
        lg: "rounded-lg",
        xl: "rounded-xl",
        "2xl": "rounded-2xl",
        "3xl": "rounded-3xl",
        "4xl": "rounded-4xl",
      },
      size: {
        xxs: "h-6 px-2 text-xs font-semibold",
        xs: "h-8 px-4 text-sm font-semibold",
        sm: "h-9 px-4 text-sm font-semibold",
        md: "h-10 px-4 text-base font-semibold",
        lg: "h-12 px-6 text-lg font-semibold",
        xl: "h-12 px-8 text-xl font-semibold",
        "2xl": "h-14 px-8 text-2xl font-semibold",
        "3xl": "h-16 px-8 text-3xl font-semibold",
        "4xl": "h-20 px-10 text-4xl font-semibold",
        icon: "h-10 w-10 p-0",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "md",
      rounded: "md",
      effect: "default",
      iconfx: "none",
    },
  },
);

function ButtonPro<T extends React.ElementType = "button">({
  className,
  variant,
  size,
  effect,
  rounded,
  iconfx,
  render,
  href,
  ...props
}: React.ComponentProps<T> &
  VariantProps<typeof buttonProVariants> & {
    /** Base UI sustituye `asChild` por `render`: el elemento a renderizar. */
    render?: React.ReactElement;
    href?: string;
  }) {
  return useRender({
    defaultTagName: href ? "a" : "button",
    render,
    props: mergeProps(
      {
        "data-slot": "button",
        className: cn(
          buttonProVariants({ variant, size, effect, rounded, iconfx }),
          className,
        ),
        ...(href ? { href } : {}),
      },
      props as Record<string, unknown>,
    ),
  });
}

export { ButtonPro, buttonProVariants };

export type ButtonProVariantProps = VariantProps<typeof buttonProVariants>;
export type ButtonProBaseProps = React.ComponentProps<"button"> & {
  href?: string;
};
export type ButtonProProps = ButtonProBaseProps &
  ButtonProVariantProps & { render?: React.ReactElement };

More from saastro

Browse all components in this library →