AnimationsSimpleApril 8, 2026
Label Rollover
Two identical text labels stacked vertically in an overflow-hidden container. On hover the stack slides up revealing the duplicate below, creating an infinite-loop text swap effect. Pure CSS.
View Full Demo →Preview
Source
demo.jsx
import LabelRollover from "./index.jsx";
import { labelRollover } from "@/content/animations/demo-data.js";
import styles from "./demo.module.css";
export default function LabelRolloverDemo() {
return (
<div className={styles.demo}>
<header className={styles.header}>
<span className={styles.logo}>No. 5</span>
<LabelRollover {...labelRollover} />
<span className={styles.cta}>Get in touch</span>
</header>
<div className={styles.hero}>
<p className={styles.label}>Design & Development Studio</p>
<h1 className={styles.heading}>Crafting Digital<br />Experiences</h1>
</div>
</div>
);
}
index.jsx
import styles from "./styles.module.css";
export default function LabelRollover({ links = [] }) {
return (
<nav className={styles.nav}>
{links.map((link) => (
<a key={link.label} href={link.href} className={styles.link}>
<span className={styles.stack}>
<span className={styles.textDefault}>{link.label}</span>
<span className={styles.textHover} aria-hidden="true">{link.label}</span>
</span>
</a>
))}
</nav>
);
}
demo.module.css
.demo {
min-height: 100vh;
background: #0a0a0a;
display: flex;
flex-direction: column;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.5rem 3rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.logo {
font-size: 1rem;
font-weight: 600;
color: #ffffff;
letter-spacing: 0.04em;
min-width: 80px;
}
/* Override link color for dark bg */
.header :global(a) {
color: rgba(255, 255, 255, 0.6) !important;
}
.header :global(a:hover) {
color: #ffffff !important;
}
.cta {
font-size: 0.8125rem;
font-weight: 500;
letter-spacing: 0.08em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.4);
min-width: 80px;
text-align: right;
}
.hero {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 4rem 2rem;
gap: 1.25rem;
}
.label {
font-size: 0.75rem;
font-weight: 500;
letter-spacing: 0.14em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.35);
}
.heading {
font-size: clamp(3rem, 8vw, 7rem);
font-weight: 500;
letter-spacing: -0.03em;
line-height: 0.95;
color: #ffffff;
}
styles.module.css
.nav {
display: flex;
gap: 2.5rem;
padding: 3rem;
justify-content: center;
align-items: center;
}
.link {
text-decoration: none;
color: #1a1a1a;
display: block;
}
.stack {
position: relative;
overflow: hidden;
display: inline-block;
line-height: 1.4;
}
.textDefault,
.textHover {
display: block;
font-size: 1.125rem;
font-weight: 500;
letter-spacing: -0.01em;
white-space: nowrap;
transition: transform 500ms cubic-bezier(0.65, 0, 0, 1);
}
.textDefault {
transform: translateY(0%);
}
.link:hover .textDefault {
transform: translateY(-150%);
}
.textHover {
position: absolute;
top: 0;
left: 0;
transform: translateY(150%);
transition-delay: 100ms;
}
.link:hover .textHover {
transform: translateY(0%);
transition-delay: 100ms;
}