Elixir UI

Action Button

ActionButton is an animated CTA component with sliding text and icon transitions, designed for high-emphasis actions with multiple visual variants.

Installation

Install with CLI

Choose your package manager.

npx @praveenlodhi/elixir-ui add action-button
pnpm dlx @praveenlodhi/elixir-ui add action-button
yarn dlx @praveenlodhi/elixir-ui add action-button
bun x @praveenlodhi/elixir-ui add action-button

Install dependencies

npm install motion lucide-react class-variance-authority clsx
pnpm add motion lucide-react class-variance-authority clsx
yarn add motion lucide-react class-variance-authority clsx
bun add motion lucide-react class-variance-authority clsx

Create component file

Create a file at components/ui/action-button.tsx and paste the ActionButton source.

components/ui/action-button.tsx
"use client";

import Link from "next/link";

import { cva, type VariantProps } from "class-variance-authority";
import clsx from "clsx";
import { ChevronDown } from "lucide-react";
import { motion } from "motion/react";

import { cn } from "../lib/utils";

const actionButtonVariants = cva(
  "group relative flex items-center justify-between overflow-hidden rounded-lg px-5 py-3 cursor-pointer text-sm font-medium transition-colors",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive:
          "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
        outline:
          "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
        secondary:
          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost:
          "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
        link: "text-primary underline-offset-4 hover:underline",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
);

export interface ActionButtonProps {
  children?: React.ReactNode;
  icon?: React.ReactNode;
  href?: string;
  className?: string;
  onClick?: React.MouseEventHandler<HTMLButtonElement>;
}

export function ActionButton({
  children = "Button Name",
  icon = <ChevronDown size={20} />,
  variant = "default",
  href,
  className,
  onClick,
}: ActionButtonProps & VariantProps<typeof actionButtonVariants>) {
  const content = (
    <>
      {/* TEXT */}
      <div className="relative h-5 overflow-hidden">
        {/* original */}
        <motion.span
          variants={{
            rest: { y: 0 },
            hover: { y: "-100%" },
          }}
          transition={{ duration: 0.15, ease: "easeOut" }}
          className="block"
        >
          {children}
        </motion.span>

        {/* replica */}
        <motion.span
          variants={{
            rest: { y: "100%" },
            hover: { y: "0%" },
          }}
          transition={{ duration: 0.12, ease: "easeOut" }}
          className={cn(
            "absolute top-0 left-0 block",
            variant === "link" && "underline"
          )}
        >
          {children}
        </motion.span>
      </div>

      {/* ICON */}
      {icon && (
        <div className="relative ml-4 h-6 w-6 overflow-hidden">
          {/* original */}
          <motion.div
            variants={{
              rest: { y: 0 },
              hover: { y: "100%" },
            }}
            transition={{ duration: 0.18, ease: "easeOut" }}
            className="flex h-full w-full items-center justify-center"
          >
            {icon}
          </motion.div>

          {/* replica */}
          <motion.div
            variants={{
              rest: { y: "-100%" },
              hover: { y: "0%" },
            }}
            transition={{ duration: 0.18, ease: "easeOut" }}
            className="absolute top-0 left-0 flex h-full w-full items-center justify-center"
          >
            {icon}
          </motion.div>
        </div>
      )}
    </>
  );

  if (href) {
    return (
      <motion.div
        initial="rest"
        whileHover="hover"
        animate="rest"
        data-variant={variant}
        className={cn(
          clsx(actionButtonVariants({ variant })),
          !icon && "justify-center",
          className
        )}
      >
        <Link href={href} className="flex w-full items-center justify-between">
          {content}
        </Link>
      </motion.div>
    );
  }

  return (
    <motion.button
      initial="rest"
      whileHover="hover"
      animate="rest"
      onClick={onClick}
      data-variant={variant}
      className={cn(
        clsx(actionButtonVariants({ variant })),
        !icon && "justify-center",
        className
      )}
    >
      {content}
    </motion.button>
  );
}

Usage

import { ActionButton } from "@workspace/ui/components/action-button";

export function Demo() {
  return (
    <ActionButton variant="outline" onClick={() => console.log("clicked")}>
      Explore Components
    </ActionButton>
  );
}

Props

Prop

Type

On this page