1 GitHub star = 1 new component.

Star the repo
Buttonsbuttonpremium

Animated Icon Button

Interactive button with smooth animated SVG icon transitions on hover.

Live Preview

Usage

Copy and paste this code into your React component:

tsx
import { AnimatedIconButton } from 'react-component-kit-ui/buttons';

<AnimatedIconButton>Continue</AnimatedIconButton>
<AnimatedIconButton variant="secondary" iconPos="left">Back</AnimatedIconButton>

Manual Installation

If you are not using the npm package, create a new file and paste the following code: src/components/AnimatedIconButton.tsx + AnimatedIconButton.css:

AnimatedIconButton.tsx
'use client';

import type { ElementType, HTMLAttributes, ReactNode } from 'react';
import './AnimatedIconButton.css';

/**
 * AnimatedIconButton โ€” A button that elegantly slides an icon into view on hover.
 *
 * @param icon - Optional. Custom icon node. Defaults to a chevron pointing in `iconPos` direction.
 * @param iconPos - Optional. 'left' | 'right'. Default 'right'.
 */
export interface AnimatedIconButtonProps extends HTMLAttributes<HTMLElement> {
  href?: string;
  target?: string;
  rel?: string;
  type?: 'button' | 'submit' | 'reset';
  variant?: 'primary' | 'secondary' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  iconPos?: 'left' | 'right';
  icon?: ReactNode;
  disabled?: boolean;
  children?: ReactNode;
}

const ChevronLeft = () => (
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
    <polyline points="15 18 9 12 15 6" />
  </svg>
);
const ChevronRight = () => (
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
    <polyline points="9 18 15 12 9 6" />
  </svg>
);

export function AnimatedIconButton({
  href,
  type = 'button',
  variant = 'primary',
  size = 'md',
  iconPos = 'right',
  icon,
  disabled = false,
  className,
  children,
  ...rest
}: AnimatedIconButtonProps) {
  const Tag: ElementType = href ? 'a' : 'button';
  const iconNode = <span className="icon-anim-btn__icon">{icon ?? (iconPos === 'left' ? <ChevronLeft /> : <ChevronRight />)}</span>;
  return (
    <Tag
      {...rest}
      href={href}
      type={href ? undefined : type}
      disabled={href ? undefined : disabled}
      aria-disabled={href && disabled ? true : undefined}
      className={['icon-anim-btn', `icon-anim-btn--${variant}`, `icon-anim-btn--${size}`, `icon-anim-btn--${iconPos}`, className]
        .filter(Boolean)
        .join(' ')}
    >
      <span className="icon-anim-btn__inner">
        {iconPos === 'left' && iconNode}
        <span className="icon-anim-btn__text">{children}</span>
        {iconPos === 'right' && iconNode}
      </span>
    </Tag>
  );
}

export default AnimatedIconButton;
AnimatedIconButton.css
/* AnimatedIconButton โ€” ported from astro-component-kit (buttons/AnimatedIconButton.astro) */

.icon-anim-btn {
  --btn-bg: var(--c-primary, #6366f1);
  --btn-color: #fff;
  --btn-hover-bg: var(--c-primary-light, #818cf8);
  
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--btn-bg);
  color: var(--btn-color);
  border: none;
  border-radius: var(--r-md, 12px);
  cursor: pointer;
  text-decoration: none;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  font-weight: 600;
  user-select: none;
  white-space: nowrap;
  overflow: hidden;
  font-family: var(--font-sans, inherit);
}

.icon-anim-btn__inner {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  transition: transform 0.3s ease;
}

.icon-anim-btn__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.25em;
  height: 1.25em;
  transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.icon-anim-btn__icon svg {
  width: 100%;
  height: 100%;
}

/* Hover Animations */
.icon-anim-btn--right:hover:not(:disabled) .icon-anim-btn__icon {
  transform: translateX(4px) scale(1.1);
}

.icon-anim-btn--left:hover:not(:disabled) .icon-anim-btn__icon {
  transform: translateX(-4px) scale(1.1);
}

.icon-anim-btn:hover:not(:disabled) {
  background: var(--btn-hover-bg);
  box-shadow: 0 4px 20px -5px color-mix(in srgb, var(--btn-bg), transparent 40%);
}

.icon-anim-btn:active:not(:disabled) {
  transform: scale(0.97);
}

.icon-anim-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  filter: grayscale(1);
}

/* Variants */
.icon-anim-btn--secondary {
  --btn-bg: rgba(255, 255, 255, 0.05);
  --btn-color: var(--c-text-1, #f8fafc);
  --btn-hover-bg: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.icon-anim-btn--ghost {
  --btn-bg: transparent;
  --btn-color: var(--c-primary-light, #818cf8);
  --btn-hover-bg: rgba(99, 102, 241, 0.08);
  border: 1px solid rgba(99, 102, 241, 0.2);
}

/* Sizes */
.icon-anim-btn--sm { padding: 0.45rem 1.2rem; font-size: 0.85rem; }
.icon-anim-btn--md { padding: 0.75rem 1.8rem; font-size: 1rem; }
.icon-anim-btn--lg { padding: 1rem 2.5rem; font-size: 1.15rem; }

Quick Info

Category
Buttons
Filename
AnimatedIconButton.tsx
Dependencies
None โ€” pure React + CSS
Tags
buttonpremium