Link Button

A lightweight animated link button for navigation, secondary actions, and product pages that need subtle interaction feedback.

Published On: June 4, 2026
Last Updated: August 17, 2026
Link Button

Overview

Link Button gives a button or link a small, useful moment of feedback: a link earns a small interaction state.

Use it where a CTA needs to feel responsive and intentional, not theatrical. A button should confirm action, state, or direction; it should not become a tiny stage play that delays the click.

The thing to watch is semantics. Keep real button and anchor elements, visible focus, instant activation, and clear loading/disabled states. Motion sits on top of the action; it does not replace it.


Install Command

npx hyperiux add link-button

Usage Code

page.jsx
import LinkButton from "@/components/effects/link-button";

export default function Page() {
    return (
        <>
        <div className="h-screen w-screen flex items-center justify-center relative">
                <div className="w-fit mx-auto">
                     <LinkButton
                        btnText="Hover me"
                        href="#"
                        iconClassName="size-[2vw] mt-[0.2vw] max-sm:size-[4vw]"
                        className="text-[2vw] hover:text-[#ff6b00] max-sm:text-[4.5vw]"
                    />
            </div>
        </div>
        </>

    );
}

Component Code

index.jsx
// Built using Hyperiux Vault: https://vault.hyperiux.com
"use client";
import { ArrowRight } from "lucide-react";
import { useLayoutEffect, useState } from "react";
const DEFAULT_HREF = "#";
const TABLET_BREAKPOINT = 1025;
export default function LinkButton({ btnText = "Hover me", mobileText = "Click me", textColor = "#ffffff", hoverColor = "#ff6b00", clickedColor = "#ff6b00", href = DEFAULT_HREF, className = "", linkProps = {}, icon: Icon = ArrowRight, iconClassName = "max-md:size-[6vw] max-[1025px]:size-[4vw]", showIcon = true, underlineHeight = 1.5, transitionDuration = 0.5, disableNavigation = false, onClick, ...props }) {
    const [isIconRotated, setIsIconRotated] = useState(false);
    const [isCompactViewport, setIsCompactViewport] = useState(false);
    const [hasMeasuredViewport, setHasMeasuredViewport] = useState(false);
    useLayoutEffect(() => {
        const onResize = () => {
            setIsCompactViewport(window.innerWidth <= TABLET_BREAKPOINT);
            setHasMeasuredViewport(true);
        };
        onResize();
        window.addEventListener("resize", onResize);
        return () => {
            window.removeEventListener("resize", onResize);
        };
    }, []);
    const onLinkClick = (event) => {
        setIsIconRotated((previousValue) => !previousValue);
        if (disableNavigation) {
            event.preventDefault();
        }
        onClick?.(event);
    };
    const { style: customStyle, ...restProps } = props;
    const displayText = hasMeasuredViewport && isCompactViewport ? mobileText : btnText;
    const mergedStyle = {
        "--underline-height": `${underlineHeight}px`,
        "--transition-duration": `${transitionDuration}s`,
        ...(hoverColor ? { "--link-hover-color": hoverColor } : null),
        ...(textColor ? { "--link-text-color": textColor } : null),
        ...(hasMeasuredViewport && isCompactViewport && isIconRotated ? { color: clickedColor } : null),
        ...customStyle,
    };
    const iconClassNames = `${isIconRotated ? "motion-safe:-rotate-45" : ""} ${!isCompactViewport ? "motion-safe:group-hover:-rotate-45" : ""} size-[1.1vw] max-[1025px]:size-[2vw] max-md:size-[3.5vw] transition-transform duration-[var(--transition-duration)] motion-reduce:rotate-0 motion-reduce:transition-none ${iconClassName}`;
    return (<>
      <a href={href} {...linkProps} {...restProps} onClick={onLinkClick} className={`group block w-fit cursor-pointer text-[1.1vw] leading-[1.2] duration-300 text-(--link-text-color) hover:text-(--link-hover-color) max-[1025px]:text-[4vw] max-md:text-[5.5vw] ${className}`} style={mergedStyle}>
        <div className="flex items-center justify-start gap-2">
          <span className={`btn-link-line relative inline-block w-fit after:absolute after:left-0 after:bottom-[-2%] after:h-[var(--underline-height)] after:w-full after:bg-current after:content-[''] after:transition-transform after:duration-[var(--transition-duration)] after:ease-[cubic-bezier(0.62,0.05,0.01,0.99)] motion-reduce:after:transition-none ${hasMeasuredViewport && isCompactViewport
            ? isIconRotated
                ? "after:origin-right after:scale-x-0 motion-safe:after:origin-left motion-safe:after:scale-x-100"
                : "after:origin-right after:scale-x-0"
            : "after:origin-right after:scale-x-0 motion-safe:group-hover:after:origin-left motion-safe:group-hover:after:scale-x-100"}`}>
            {displayText}
          </span>

          <span className="sr-only">About {href}</span>

          {showIcon && Icon && <Icon className={iconClassNames}/>}
        </div>
      </a>

      <style jsx>{`
        li :global(.btn-link-line)::after {
          bottom: -20%;
        }
      `}</style>
    </>);
}

Example Production Use Case

Use this as button-interaction implementation guidance. Verify the shipped element semantics, anchor/button mode, disabled and loading states, focus-visible behavior, keyboard activation, and reduced-motion handling before relying on exact props, defaults, imports, or installation steps.


Best Used For

  • Secondary actions and navigation links that need a small designed state, not drama.
  • Product and docs pages where lightweight feedback supports orientation.
  • Link Button adds feedback to an action without weakening semantics, focus, or activation speed.

Not For

Not for destructive actions, unclear states, or places where animation hides the label or delays activation.


Performance Budget

Keep feedback fast, avoid layout-triggering letter movement, and preserve instant activation.


Accessibility and Mobile

Use real buttons or anchors, visible focus-visible styles, and clear loading/disabled states. Touch targets should stay large enough on mobile.


Common Mistakes

  • Building Link Button with divs instead of real buttons or links.
  • Hiding the label during hover.
  • Forgetting loading and disabled states.

Changelog

v1.0.1

Jul 15, 2026
Removed an errant scale-150 class that rendered the link at 1.5x size

Props

PropTypeDefaultDescription
btnTextstringHover meButton label text.
textColorcolor#ffffffResting label and underline color.
hoverColorcolor#ff6b00Hover label and underline color.
underlineHeightnumber1.5Underline thickness in pixels.
showIconbooleantrueShows or hides the arrow icon.
transitionDurationnumber0.5Animation duration in seconds.

Frequently Asked Questions

When should I use Link Button?

Use it when a navigation or secondary link needs a small, lightweight interaction state — without delaying the action.

Request a Custom Button Animation

Need a custom effect? Tell us what to create.