Sliding Underline
The underline draws in from the left when you hover a link and leaves through the right, so it reads as passing through.
Create navigation links with a sliding underline in pure CSS. Each link is position: relative with an ::after line (height 2px, bottom -4px, full width, accent colour) set to transform: scaleX(0) with transform-origin: right. On :hover and :focus-visible, switch to transform-origin: left and scaleX(1). Transition transform over 300ms with cubic-bezier(0.16, 1, 0.3, 1). Because the origin flips, the line enters from the left and exits to the right. Under prefers-reduced-motion, show the line instantly without the slide. The links read "Work", "About", and "Contact".
Source
'use client'
import s from './demo.module.css'
export default function UnderlineSlide({ labels }: { labels: Record<string, string> }) {
return (
<nav className={s.nav}>
{[labels.a, labels.b, labels.c].map((t) => (
<a key={t} href="#" className={s.link} onClick={(e) => e.preventDefault()}>
{t}
</a>
))}
</nav>
)
}
.nav {
display: flex;
gap: 28px;
}
.link {
position: relative;
color: var(--stage-ink);
font: 500 var(--text-xl) / 1.2 var(--font-display);
text-decoration: none;
}
.link::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: -4px;
height: 2px;
background: var(--stage-accent);
transform: scaleX(0);
transform-origin: right;
transition: transform 300ms var(--ease-out);
}
.link:hover::after,
.link:focus-visible::after {
transform: scaleX(1);
transform-origin: left;
}
.link:focus-visible { outline: none; }
@media (prefers-reduced-motion: reduce) {
.link::after { transition: none; }
}