Underline Link
Text link with a line that draws in from the left and leaves to the right.
From the Nitip template — see it in contextimport { UnderlineLink } from "@/components/ui/underline-link";
export default function UnderlineLinkDemo() {
return (
<div className="flex flex-wrap items-center justify-center gap-10">
<UnderlineLink href="#">All projects</UnderlineLink>
<UnderlineLink href="#" arrow={false} thickness={2}>
Read the story
</UnderlineLink>
</div>
);
}Installation
$ npx shadcn@latest add https://framebrick.co/r/underline-link.json1. Install the dependencies.
npm install motion clsx tailwind-merge2. Add
cn()tolib/utils.ts(skip if shadcn already created it).import { clsx, type ClassValue } from "clsx"; import { extendTailwindMerge } from "tailwind-merge"; /* tailwind-merge must know the site's type scale (globals.css) is a font size, not a text color — otherwise cn("text-h6 text-muted-foreground") drops text-h6. */ const twMerge = extendTailwindMerge({ extend: { classGroups: { "font-size": [{ text: ["display", "h1", "h2", "h3", "h4", "h5", "h6"] }] } }, }); export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }3. Copy the component into
components/ui/underline-link.tsx."use client"; import * as React from "react"; import Link from "next/link"; import { motion, type HTMLMotionProps, type Transition } from "motion/react"; import { cn } from "@/lib/utils"; /* * Underline Link — a text link with a line that draws in on hover (from the Nitip "Secondary Button"). * The line grows from the left and leaves to the right; the optional arrow turns from ↘ to →. * External http(s) links open in a new tab. */ const spring: Transition = { type: "spring", bounce: 0.2, duration: 0.4 }; const MotionLink = motion.create(Link); export type UnderlineLinkProps = Omit<HTMLMotionProps<"a">, "href" | "children"> & { href: string; /** Show the turning arrow after the label. */ arrow?: boolean; /** Line thickness. */ thickness?: 1 | 2; children?: React.ReactNode; }; export function UnderlineLink({ href, arrow = true, thickness = 1, className, children, ...props }: UnderlineLinkProps) { const external = /^https?:\/\//.test(href); return ( <MotionLink href={href} {...(external ? { target: "_blank", rel: "noopener noreferrer" } : {})} {...props} initial="rest" animate="rest" whileHover="hover" whileFocus="hover" className={cn( "relative inline-flex items-center gap-1 pb-0.5 text-sm font-medium whitespace-nowrap text-foreground outline-none", "rounded-xs focus-visible:ring-[3px] focus-visible:ring-ring/50", className, )} > {children} {arrow && ( <motion.svg aria-hidden viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" className="size-4" variants={{ rest: { rotate: 0 }, hover: { rotate: -45 } }} transition={spring} > <path d="M4.5 4.5l7 7M11.5 5.5v6h-6" /> </motion.svg> )} <motion.span aria-hidden className="absolute inset-x-0 bottom-0 bg-current" style={{ height: thickness }} variants={{ rest: { scaleX: 0, originX: 1 }, hover: { scaleX: 1, originX: 0 } }} transition={{ scaleX: spring, originX: { duration: 0 } }} /> </MotionLink> ); }
Usage
import { UnderlineLink } from "@/components/ui/underline-link";
<UnderlineLink href="/projects">All projects</UnderlineLink>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| href | string | — | Link target. http(s) links open in a new tab. |
| arrow | boolean | true | Show the arrow that turns from ↘ to → on hover. |
| thickness | 1 | 2 | 1 | Line thickness in px. |
| …props | anchor props | — | Passed to the link. |
- The line also draws in on keyboard focus.
AI prompt
Paste into v0, Lovable, Bolt or Claude to rebuild this component in your own project.
Create a React + Tailwind CSS + Motion (motion/react) component called UnderlineLink using Next.js Link. - Inline-flex, text-sm font-medium text-foreground (shadcn defaults), 2px bottom padding. - An absolutely positioned line at the bottom (1px or 2px, bg-current). At rest scaleX 0; on hover scaleX 1. Grow from the left (transform-origin left) and shrink out to the right (transform-origin right) — switch the origin instantly. - Optional down-right arrow after the label that rotates -45° on hover so it points right. - Spring for both: bounce 0.2, duration 0.4s. The same animation runs on keyboard focus. - External http(s) links open in a new tab with rel noopener noreferrer. Colors from the theme (text-foreground).











