8 CSS Navbar Designs08 / 08
CSS Navbar with Underline Hover Animation
A minimalist CSS navbar with three distinct underline hover animation techniques side-by-side — proving that the most elegant nav effects come from doing less, not more. Variant 1 (the production nav at top) uses a JS-driven magnetic ink line: a single absolutely-positioned 1.5px rail glides between hovered items via getBoundingClientRect, with a red static line marking the active item. Variant 2 is pure CSS — each link owns an `::after` pseudo-element that starts at `left: 50%; right: 50%` (zero width, centered) and expands outward to the padding edge on hover, blooming symmetrically. Variant 3 uses the same `::after` trick but transitions only one edge, producing a left-anchored directional wipe. The page includes a Libre Baskerville serif "art of the quiet hover" hero, an A–Z alphabet ticker where every letter gets the underline treatment, and a 4-row showcase list where each title gets a 2px red underline wipe on hover. Libre Baskerville + Instrument Sans. Best for editorial sites, personal portfolios, design studio navs, content-led blogs, minimalist typography-first layouts.
Published Updated
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<section class="nb-und" aria-label="CSS navbar with underline hover animation demo">
<nav aria-label="Primary">
<div class="nav-inner">
<a href="#" class="brand">Thesis</a>
<div class="nav-links-wrap" data-nb-und-wrap>
<div class="ink-line" data-nb-und-line aria-hidden="true"></div>
<div class="ink-line-static" data-nb-und-static aria-hidden="true"></div>
<ul class="nav-links" data-nb-und-nav>
<li><a href="#" class="active"><span>Studio</span></a></li>
<li><a href="#"><span>Work</span></a></li>
<li><a href="#"><span>Writing</span></a></li>
<li><a href="#"><span>Process</span></a></li>
<li><a href="#"><span>Archive</span></a></li>
<li><a href="#"><span>Contact</span></a></li>
</ul>
</div>
<div class="nav-spacer"></div>
<div class="nav-end">
<a href="#">Index</a>
<div class="nav-end-dot" aria-hidden="true"></div>
<a href="#">RSS</a>
<div class="nav-end-dot" aria-hidden="true"></div>
<button class="btn-nav" type="button">Inquire</button>
</div>
</div>
</nav>
<div class="page">
<div class="hero">
<div class="hero-left">
<h1>The art of<br>the <em>quiet</em><br>hover.</h1>
<p>Three distinct CSS underline animation techniques — each achieving elegance through restraint. The navbar above uses a magnetic sliding ink line that follows your cursor. Below, explore every method.</p>
</div>
<div class="hero-right">
<p>"Typography is a <strong>silent conversation</strong> — the best underlines join it without interrupting. Hover the navigation above and each demo below to see them in motion."</p>
</div>
</div>
<div class="section-rule"></div>
<section class="section">
<div class="variant-label">
<span class="variant-num">01</span>
<div class="variant-info">
<h3>Magnetic Sliding Line — Single Global Rail</h3>
<p>One line glides beneath the entire nav, tracking the hovered link. Active item uses a static accent-colored line.</p>
</div>
</div>
<div class="demo-nav">
<div class="nav-links-wrap demo-wrap" data-nb-und-demo-wrap>
<div class="ink-line" data-nb-und-demo-line aria-hidden="true"></div>
<div class="ink-line-static" data-nb-und-demo-static aria-hidden="true"></div>
<ul class="nav-links" data-nb-und-demo-nav>
<li><a href="#" class="active"><span>Objects</span></a></li>
<li><a href="#"><span>Spaces</span></a></li>
<li><a href="#"><span>Systems</span></a></li>
<li><a href="#"><span>Texts</span></a></li>
<li><a href="#"><span>People</span></a></li>
</ul>
</div>
</div>
<p class="code-note">
One <code>.ink-line</code> element is positioned with JS using <code>getBoundingClientRect()</code> on each hover target.
CSS <code>transition: left, width</code> with <code>cubic-bezier(0.25,1,0.5,1)</code> creates the magnetic feel.
The static <code>.ink-line-static</code> marks the active item in accent red.
</p>
</section>
<div class="section-rule"></div>
<section class="section">
<div class="variant-label">
<span class="variant-num">02</span>
<div class="variant-info">
<h3>Center-Bloom — Per-Link <code>::after</code> Pseudo-Element</h3>
<p>Each link owns its underline. On hover, it expands from the center outward — no JavaScript needed at all.</p>
</div>
</div>
<div class="demo-nav">
<ul class="nav-links-v2">
<li><a href="#" class="active">Foundations</a></li>
<li><a href="#">Components</a></li>
<li><a href="#">Patterns</a></li>
<li><a href="#">Motion</a></li>
<li><a href="#">Tokens</a></li>
</ul>
</div>
<p class="code-note">
CSS only. Each <code>a::after</code> starts at <code>left: 50%; right: 50%</code> (zero width, centered).
On <code>:hover</code>, <code>left</code> and <code>right</code> both animate to the padding value — blooming outward symmetrically.
</p>
</section>
<div class="section-rule"></div>
<section class="section">
<div class="variant-label">
<span class="variant-num">03</span>
<div class="variant-info">
<h3>Left-Wipe — Directional Reveal</h3>
<p>The underline sweeps in from the left, anchored to the link's left edge. Clean, directional, editorial.</p>
</div>
</div>
<div class="demo-nav">
<ul class="nav-links-v3">
<li><a href="#" class="active">Editorial</a></li>
<li><a href="#">Research</a></li>
<li><a href="#">Narrative</a></li>
<li><a href="#">Margins</a></li>
<li><a href="#">Colophon</a></li>
</ul>
</div>
<p class="code-note">
CSS only. <code>a::before</code> starts at <code>left: 50%; right: 50%</code> and transitions to <code>left: 1.1rem; right: 1.1rem</code>.
The asymmetric easing of <code>left</code> gives it the wipe directionality.
</p>
</section>
<div class="section-rule"></div>
<div class="alphabet-demo">
<p class="alpha-label">Every link, same principle — hover any letter</p>
<div class="alpha-links" data-nb-und-alpha></div>
</div>
<div class="section-rule"></div>
<div class="showcase">
<div class="showcase-item">
<span class="showcase-title">Clarity</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 01</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
<div class="showcase-item">
<span class="showcase-title">Restraint</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 02</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
<div class="showcase-item">
<span class="showcase-title">Intention</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 03</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
<div class="showcase-item">
<span class="showcase-title">Motion</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 04</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
</div>
</div>
</section><section class="nb-und" aria-label="CSS navbar with underline hover animation demo">
<nav aria-label="Primary">
<div class="nav-inner">
<a href="#" class="brand">Thesis</a>
<div class="nav-links-wrap" data-nb-und-wrap>
<div class="ink-line" data-nb-und-line aria-hidden="true"></div>
<div class="ink-line-static" data-nb-und-static aria-hidden="true"></div>
<ul class="nav-links" data-nb-und-nav>
<li><a href="#" class="active"><span>Studio</span></a></li>
<li><a href="#"><span>Work</span></a></li>
<li><a href="#"><span>Writing</span></a></li>
<li><a href="#"><span>Process</span></a></li>
<li><a href="#"><span>Archive</span></a></li>
<li><a href="#"><span>Contact</span></a></li>
</ul>
</div>
<div class="nav-spacer"></div>
<div class="nav-end">
<a href="#">Index</a>
<div class="nav-end-dot" aria-hidden="true"></div>
<a href="#">RSS</a>
<div class="nav-end-dot" aria-hidden="true"></div>
<button class="btn-nav" type="button">Inquire</button>
</div>
</div>
</nav>
<div class="page">
<div class="hero">
<div class="hero-left">
<h1>The art of<br>the <em>quiet</em><br>hover.</h1>
<p>Three distinct CSS underline animation techniques — each achieving elegance through restraint. The navbar above uses a magnetic sliding ink line that follows your cursor. Below, explore every method.</p>
</div>
<div class="hero-right">
<p>"Typography is a <strong>silent conversation</strong> — the best underlines join it without interrupting. Hover the navigation above and each demo below to see them in motion."</p>
</div>
</div>
<div class="section-rule"></div>
<section class="section">
<div class="variant-label">
<span class="variant-num">01</span>
<div class="variant-info">
<h3>Magnetic Sliding Line — Single Global Rail</h3>
<p>One line glides beneath the entire nav, tracking the hovered link. Active item uses a static accent-colored line.</p>
</div>
</div>
<div class="demo-nav">
<div class="nav-links-wrap demo-wrap" data-nb-und-demo-wrap>
<div class="ink-line" data-nb-und-demo-line aria-hidden="true"></div>
<div class="ink-line-static" data-nb-und-demo-static aria-hidden="true"></div>
<ul class="nav-links" data-nb-und-demo-nav>
<li><a href="#" class="active"><span>Objects</span></a></li>
<li><a href="#"><span>Spaces</span></a></li>
<li><a href="#"><span>Systems</span></a></li>
<li><a href="#"><span>Texts</span></a></li>
<li><a href="#"><span>People</span></a></li>
</ul>
</div>
</div>
<p class="code-note">
One <code>.ink-line</code> element is positioned with JS using <code>getBoundingClientRect()</code> on each hover target.
CSS <code>transition: left, width</code> with <code>cubic-bezier(0.25,1,0.5,1)</code> creates the magnetic feel.
The static <code>.ink-line-static</code> marks the active item in accent red.
</p>
</section>
<div class="section-rule"></div>
<section class="section">
<div class="variant-label">
<span class="variant-num">02</span>
<div class="variant-info">
<h3>Center-Bloom — Per-Link <code>::after</code> Pseudo-Element</h3>
<p>Each link owns its underline. On hover, it expands from the center outward — no JavaScript needed at all.</p>
</div>
</div>
<div class="demo-nav">
<ul class="nav-links-v2">
<li><a href="#" class="active">Foundations</a></li>
<li><a href="#">Components</a></li>
<li><a href="#">Patterns</a></li>
<li><a href="#">Motion</a></li>
<li><a href="#">Tokens</a></li>
</ul>
</div>
<p class="code-note">
CSS only. Each <code>a::after</code> starts at <code>left: 50%; right: 50%</code> (zero width, centered).
On <code>:hover</code>, <code>left</code> and <code>right</code> both animate to the padding value — blooming outward symmetrically.
</p>
</section>
<div class="section-rule"></div>
<section class="section">
<div class="variant-label">
<span class="variant-num">03</span>
<div class="variant-info">
<h3>Left-Wipe — Directional Reveal</h3>
<p>The underline sweeps in from the left, anchored to the link's left edge. Clean, directional, editorial.</p>
</div>
</div>
<div class="demo-nav">
<ul class="nav-links-v3">
<li><a href="#" class="active">Editorial</a></li>
<li><a href="#">Research</a></li>
<li><a href="#">Narrative</a></li>
<li><a href="#">Margins</a></li>
<li><a href="#">Colophon</a></li>
</ul>
</div>
<p class="code-note">
CSS only. <code>a::before</code> starts at <code>left: 50%; right: 50%</code> and transitions to <code>left: 1.1rem; right: 1.1rem</code>.
The asymmetric easing of <code>left</code> gives it the wipe directionality.
</p>
</section>
<div class="section-rule"></div>
<div class="alphabet-demo">
<p class="alpha-label">Every link, same principle — hover any letter</p>
<div class="alpha-links" data-nb-und-alpha></div>
</div>
<div class="section-rule"></div>
<div class="showcase">
<div class="showcase-item">
<span class="showcase-title">Clarity</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 01</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
<div class="showcase-item">
<span class="showcase-title">Restraint</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 02</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
<div class="showcase-item">
<span class="showcase-title">Intention</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 03</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
<div class="showcase-item">
<span class="showcase-title">Motion</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 04</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
</div>
</div>
</section>/* ─── 08 Thesis — CSS navbar underline hover animation UI ─────── */
@import url('https://fonts.googleapis.com/css2?family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=Instrument+Sans:ital,wght@0,400;0,500;0,600;1,400&display=swap');
.nb-und {
--nb-und-paper: #f8f6f1;
--nb-und-ink: #1a1916;
--nb-und-ink-2: #6b6760;
--nb-und-ink-3: #b5b2ac;
--nb-und-rule: #e0ddd6;
--nb-und-red: #c93a24;
--nb-und-nav-h: 70px;
position: relative;
width: 100%;
min-height: 1600px;
background: var(--nb-und-paper);
color: var(--nb-und-ink);
font-family: 'Instrument Sans', sans-serif;
overflow: clip;
box-sizing: border-box;
}
.nb-und *,
.nb-und *::before,
.nb-und *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.nb-und nav {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 100;
height: var(--nb-und-nav-h);
background: var(--nb-und-paper);
border-bottom: 1px solid var(--nb-und-rule);
}
.nb-und .nav-inner {
max-width: 1440px;
margin: 0 auto;
height: 100%;
display: flex;
align-items: center;
padding: 0 3rem;
}
.nb-und .brand {
font-family: 'Libre Baskerville', serif;
font-size: 1.2rem;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--nb-und-ink);
text-decoration: none;
flex-shrink: 0;
margin-right: 4rem;
}
.nb-und .nav-links-wrap {
flex: 1;
position: relative;
display: flex;
align-items: center;
}
.nb-und .nav-links {
display: flex;
align-items: center;
list-style: none;
gap: 0;
}
.nb-und .ink-line {
position: absolute;
bottom: -1px;
height: 1.5px;
background: var(--nb-und-ink);
transition: left 0.38s cubic-bezier(0.25, 1, 0.5, 1), width 0.38s cubic-bezier(0.25, 1, 0.5, 1), opacity 0.25s;
pointer-events: none;
opacity: 0;
}
.nb-und .ink-line.visible {
opacity: 1;
}
.nb-und .ink-line-static {
position: absolute;
bottom: -1px;
height: 1.5px;
background: var(--nb-und-red);
pointer-events: none;
transition: left 0.4s cubic-bezier(0.25, 1, 0.5, 1), width 0.4s;
}
.nb-und .nav-links a {
display: flex;
align-items: center;
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
text-decoration: none;
padding: 0 1.1rem;
height: var(--nb-und-nav-h);
transition: color 0.22s;
position: relative;
white-space: nowrap;
}
.nb-und .nav-links a:hover {
color: var(--nb-und-ink);
}
.nb-und .nav-links a.active {
color: var(--nb-und-ink);
}
.nb-und .nav-links a span {
display: inline-block;
transition: letter-spacing 0.35s cubic-bezier(0.25, 1, 0.5, 1);
}
.nb-und .nav-links a:hover span {
letter-spacing: 0.09em;
}
.nb-und .nav-links a.active span {
letter-spacing: 0.02em;
}
/* Variant 2 — center-bloom (pure CSS) */
.nb-und .nav-links-v2 {
display: flex;
align-items: center;
list-style: none;
gap: 0;
}
.nb-und .nav-links-v2 a {
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
text-decoration: none;
padding: 0 1.1rem;
height: var(--nb-und-nav-h);
position: relative;
white-space: nowrap;
transition: color 0.22s;
}
.nb-und .nav-links-v2 a:hover {
color: var(--nb-und-ink);
}
.nb-und .nav-links-v2 a.active {
color: var(--nb-und-ink);
}
.nb-und .nav-links-v2 a::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
right: 50%;
height: 1.5px;
background: var(--nb-und-ink);
transition: left 0.35s cubic-bezier(0.25, 1, 0.5, 1), right 0.35s cubic-bezier(0.25, 1, 0.5, 1);
}
.nb-und .nav-links-v2 a.active::after {
background: var(--nb-und-red);
left: 1.1rem;
right: 1.1rem;
}
.nb-und .nav-links-v2 a:hover::after {
left: 1.1rem;
right: 1.1rem;
}
/* Variant 3 — left-wipe */
.nb-und .nav-links-v3 {
display: flex;
align-items: center;
list-style: none;
gap: 0;
}
.nb-und .nav-links-v3 a {
display: flex;
align-items: center;
height: var(--nb-und-nav-h);
padding: 0 1.1rem;
text-decoration: none;
white-space: nowrap;
position: relative;
overflow: hidden;
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
transition: color 0.22s;
}
.nb-und .nav-links-v3 a:hover,
.nb-und .nav-links-v3 a.active {
color: var(--nb-und-ink);
}
.nb-und .nav-links-v3 a::before {
content: '';
position: absolute;
bottom: 0;
left: 50%;
right: 50%;
height: 1.5px;
background: var(--nb-und-ink);
transition: left 0.3s ease, right 0.3s ease;
}
.nb-und .nav-links-v3 a:hover::before {
left: 1.1rem;
right: 1.1rem;
}
.nb-und .nav-links-v3 a.active::before {
left: 1.1rem;
right: 1.1rem;
background: var(--nb-und-red);
}
.nb-und .nav-spacer {
flex: 1;
}
.nb-und .nav-end {
display: flex;
align-items: center;
gap: 1.5rem;
flex-shrink: 0;
}
.nb-und .nav-end a {
font-size: 0.78rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
text-decoration: none;
transition: color 0.2s;
position: relative;
}
.nb-und .nav-end a::after {
content: '';
position: absolute;
bottom: -2px;
left: 50%;
right: 50%;
height: 1px;
background: var(--nb-und-ink);
transition: left 0.3s ease, right 0.3s ease;
}
.nb-und .nav-end a:hover {
color: var(--nb-und-ink);
}
.nb-und .nav-end a:hover::after {
left: 0;
right: 0;
}
.nb-und .nav-end-dot {
width: 3px;
height: 3px;
border-radius: 50%;
background: var(--nb-und-rule);
}
.nb-und .btn-nav {
font-family: 'Instrument Sans', sans-serif;
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.08em;
color: var(--nb-und-paper);
background: var(--nb-und-ink);
border: none;
padding: 8px 18px;
border-radius: 2px;
cursor: pointer;
transition: background 0.2s;
}
.nb-und .btn-nav:hover {
background: var(--nb-und-red);
}
.nb-und .page {
padding-top: var(--nb-und-nav-h);
}
.nb-und .hero {
padding: 6rem 3rem 4rem;
max-width: 1440px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6rem;
align-items: end;
}
.nb-und .hero-left h1 {
font-family: 'Libre Baskerville', serif;
font-size: clamp(3rem, 5vw, 5.5rem);
font-weight: 700;
letter-spacing: -0.03em;
line-height: 1.08;
color: var(--nb-und-ink);
margin-bottom: 1.5rem;
}
.nb-und .hero-left h1 em {
font-style: italic;
font-weight: 400;
color: var(--nb-und-red);
}
.nb-und .hero-left p {
font-size: 1rem;
color: var(--nb-und-ink-2);
line-height: 1.7;
max-width: 44ch;
}
.nb-und .hero-right {
padding-bottom: 1rem;
}
.nb-und .hero-right p {
font-family: 'Libre Baskerville', serif;
font-size: 1.3rem;
font-style: italic;
font-weight: 400;
color: var(--nb-und-ink-2);
line-height: 1.6;
max-width: 36ch;
}
.nb-und .hero-right p strong {
color: var(--nb-und-ink);
font-style: normal;
font-weight: 700;
}
.nb-und .section-rule {
height: 1px;
background: var(--nb-und-rule);
margin: 0 3rem;
}
.nb-und .section {
padding: 5rem 3rem;
max-width: 1440px;
margin: 0 auto;
}
.nb-und .variant-label {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
}
.nb-und .variant-num {
font-family: 'Libre Baskerville', serif;
font-size: 3rem;
font-weight: 400;
color: var(--nb-und-rule);
line-height: 1;
}
.nb-und .variant-info h3 {
font-size: 0.88rem;
font-weight: 600;
color: var(--nb-und-ink);
letter-spacing: 0.02em;
margin-bottom: 3px;
}
.nb-und .variant-info p {
font-size: 0.78rem;
color: var(--nb-und-ink-2);
line-height: 1.5;
}
.nb-und .demo-nav {
border: 1px solid var(--nb-und-rule);
border-radius: 4px;
background: var(--nb-und-paper);
height: 60px;
display: flex;
align-items: center;
padding: 0 2rem;
position: relative;
margin-bottom: 1.5rem;
overflow: hidden;
}
.nb-und .demo-wrap {
position: relative;
display: flex;
align-items: center;
}
.nb-und .code-note {
font-family: 'Instrument Sans', monospace;
font-size: 0.72rem;
color: var(--nb-und-ink-3);
line-height: 1.7;
background: #f2f0ea;
border-left: 2px solid var(--nb-und-rule);
padding: 0.75rem 1rem;
border-radius: 0 3px 3px 0;
margin-bottom: 3rem;
}
.nb-und .code-note code {
font-family: monospace;
color: var(--nb-und-red);
font-size: 0.7rem;
}
.nb-und .alphabet-demo {
margin: 4rem 3rem;
padding: 3rem;
border: 1px solid var(--nb-und-rule);
border-radius: 4px;
text-align: center;
}
.nb-und .alpha-label {
font-size: 0.62rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--nb-und-ink-3);
margin-bottom: 2rem;
}
.nb-und .alpha-links {
display: flex;
align-items: center;
justify-content: center;
gap: 0;
flex-wrap: wrap;
}
.nb-und .alpha-links a {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
font-family: 'Libre Baskerville', serif;
font-size: 1rem;
color: var(--nb-und-ink-3);
text-decoration: none;
position: relative;
transition: color 0.2s;
border-radius: 4px;
}
.nb-und .alpha-links a::after {
content: '';
position: absolute;
bottom: 4px;
left: 50%;
right: 50%;
height: 1px;
background: var(--nb-und-ink);
transition: left 0.3s ease, right 0.3s ease;
}
.nb-und .alpha-links a:hover {
color: var(--nb-und-ink);
}
.nb-und .alpha-links a:hover::after {
left: 8px;
right: 8px;
}
.nb-und .showcase {
padding: 5rem 3rem;
border-top: 1px solid var(--nb-und-rule);
max-width: 1440px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 0;
}
.nb-und .showcase-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 2rem 0;
border-bottom: 1px solid var(--nb-und-rule);
cursor: pointer;
overflow: hidden;
position: relative;
}
.nb-und .showcase-title {
font-family: 'Libre Baskerville', serif;
font-size: clamp(2rem, 4vw, 4rem);
font-weight: 400;
color: var(--nb-und-ink);
line-height: 1;
position: relative;
display: inline-block;
transition: color 0.2s;
}
.nb-und .showcase-title::after {
content: '';
position: absolute;
bottom: 4px;
left: 0;
right: 100%;
height: 2px;
background: var(--nb-und-red);
transition: right 0.5s cubic-bezier(0.25, 1, 0.5, 1);
}
.nb-und .showcase-item:hover .showcase-title::after {
right: 0;
}
.nb-und .showcase-item:hover .showcase-title {
color: var(--nb-und-red);
}
.nb-und .showcase-right {
display: flex;
align-items: center;
gap: 1rem;
}
.nb-und .showcase-meta {
font-size: 0.72rem;
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--nb-und-ink-3);
}
.nb-und .showcase-arrow {
font-size: 1.5rem;
color: var(--nb-und-ink-3);
transform: translateX(-8px);
transition: transform 0.3s ease, color 0.2s;
}
.nb-und .showcase-item:hover .showcase-arrow {
transform: translateX(0);
color: var(--nb-und-red);
}
@media (max-width: 900px) {
.nb-und .hero {
grid-template-columns: 1fr;
gap: 2rem;
padding: 3rem 1.5rem 2rem;
}
.nb-und .nav-end a,
.nb-und .nav-end-dot {
display: none;
}
.nb-und .section {
padding: 3rem 1.5rem;
}
.nb-und .alphabet-demo {
margin: 2rem 1.5rem;
padding: 2rem 1rem;
}
.nb-und .showcase {
padding: 3rem 1.5rem;
}
.nb-und .demo-nav {
padding: 0 1rem;
}
.nb-und nav .nav-inner {
padding: 0 1.5rem;
}
}
@media (prefers-reduced-motion: reduce) {
.nb-und * {
transition: none !important;
}
}/* ─── 08 Thesis — CSS navbar underline hover animation UI ─────── */
@import url('https://fonts.googleapis.com/css2?family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=Instrument+Sans:ital,wght@0,400;0,500;0,600;1,400&display=swap');
.nb-und {
--nb-und-paper: #f8f6f1;
--nb-und-ink: #1a1916;
--nb-und-ink-2: #6b6760;
--nb-und-ink-3: #b5b2ac;
--nb-und-rule: #e0ddd6;
--nb-und-red: #c93a24;
--nb-und-nav-h: 70px;
position: relative;
width: 100%;
min-height: 1600px;
background: var(--nb-und-paper);
color: var(--nb-und-ink);
font-family: 'Instrument Sans', sans-serif;
overflow: clip;
box-sizing: border-box;
}
.nb-und *,
.nb-und *::before,
.nb-und *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.nb-und nav {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 100;
height: var(--nb-und-nav-h);
background: var(--nb-und-paper);
border-bottom: 1px solid var(--nb-und-rule);
}
.nb-und .nav-inner {
max-width: 1440px;
margin: 0 auto;
height: 100%;
display: flex;
align-items: center;
padding: 0 3rem;
}
.nb-und .brand {
font-family: 'Libre Baskerville', serif;
font-size: 1.2rem;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--nb-und-ink);
text-decoration: none;
flex-shrink: 0;
margin-right: 4rem;
}
.nb-und .nav-links-wrap {
flex: 1;
position: relative;
display: flex;
align-items: center;
}
.nb-und .nav-links {
display: flex;
align-items: center;
list-style: none;
gap: 0;
}
.nb-und .ink-line {
position: absolute;
bottom: -1px;
height: 1.5px;
background: var(--nb-und-ink);
transition: left 0.38s cubic-bezier(0.25, 1, 0.5, 1), width 0.38s cubic-bezier(0.25, 1, 0.5, 1), opacity 0.25s;
pointer-events: none;
opacity: 0;
}
.nb-und .ink-line.visible {
opacity: 1;
}
.nb-und .ink-line-static {
position: absolute;
bottom: -1px;
height: 1.5px;
background: var(--nb-und-red);
pointer-events: none;
transition: left 0.4s cubic-bezier(0.25, 1, 0.5, 1), width 0.4s;
}
.nb-und .nav-links a {
display: flex;
align-items: center;
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
text-decoration: none;
padding: 0 1.1rem;
height: var(--nb-und-nav-h);
transition: color 0.22s;
position: relative;
white-space: nowrap;
}
.nb-und .nav-links a:hover {
color: var(--nb-und-ink);
}
.nb-und .nav-links a.active {
color: var(--nb-und-ink);
}
.nb-und .nav-links a span {
display: inline-block;
transition: letter-spacing 0.35s cubic-bezier(0.25, 1, 0.5, 1);
}
.nb-und .nav-links a:hover span {
letter-spacing: 0.09em;
}
.nb-und .nav-links a.active span {
letter-spacing: 0.02em;
}
/* Variant 2 — center-bloom (pure CSS) */
.nb-und .nav-links-v2 {
display: flex;
align-items: center;
list-style: none;
gap: 0;
}
.nb-und .nav-links-v2 a {
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
text-decoration: none;
padding: 0 1.1rem;
height: var(--nb-und-nav-h);
position: relative;
white-space: nowrap;
transition: color 0.22s;
}
.nb-und .nav-links-v2 a:hover {
color: var(--nb-und-ink);
}
.nb-und .nav-links-v2 a.active {
color: var(--nb-und-ink);
}
.nb-und .nav-links-v2 a::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
right: 50%;
height: 1.5px;
background: var(--nb-und-ink);
transition: left 0.35s cubic-bezier(0.25, 1, 0.5, 1), right 0.35s cubic-bezier(0.25, 1, 0.5, 1);
}
.nb-und .nav-links-v2 a.active::after {
background: var(--nb-und-red);
left: 1.1rem;
right: 1.1rem;
}
.nb-und .nav-links-v2 a:hover::after {
left: 1.1rem;
right: 1.1rem;
}
/* Variant 3 — left-wipe */
.nb-und .nav-links-v3 {
display: flex;
align-items: center;
list-style: none;
gap: 0;
}
.nb-und .nav-links-v3 a {
display: flex;
align-items: center;
height: var(--nb-und-nav-h);
padding: 0 1.1rem;
text-decoration: none;
white-space: nowrap;
position: relative;
overflow: hidden;
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
transition: color 0.22s;
}
.nb-und .nav-links-v3 a:hover,
.nb-und .nav-links-v3 a.active {
color: var(--nb-und-ink);
}
.nb-und .nav-links-v3 a::before {
content: '';
position: absolute;
bottom: 0;
left: 50%;
right: 50%;
height: 1.5px;
background: var(--nb-und-ink);
transition: left 0.3s ease, right 0.3s ease;
}
.nb-und .nav-links-v3 a:hover::before {
left: 1.1rem;
right: 1.1rem;
}
.nb-und .nav-links-v3 a.active::before {
left: 1.1rem;
right: 1.1rem;
background: var(--nb-und-red);
}
.nb-und .nav-spacer {
flex: 1;
}
.nb-und .nav-end {
display: flex;
align-items: center;
gap: 1.5rem;
flex-shrink: 0;
}
.nb-und .nav-end a {
font-size: 0.78rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
text-decoration: none;
transition: color 0.2s;
position: relative;
}
.nb-und .nav-end a::after {
content: '';
position: absolute;
bottom: -2px;
left: 50%;
right: 50%;
height: 1px;
background: var(--nb-und-ink);
transition: left 0.3s ease, right 0.3s ease;
}
.nb-und .nav-end a:hover {
color: var(--nb-und-ink);
}
.nb-und .nav-end a:hover::after {
left: 0;
right: 0;
}
.nb-und .nav-end-dot {
width: 3px;
height: 3px;
border-radius: 50%;
background: var(--nb-und-rule);
}
.nb-und .btn-nav {
font-family: 'Instrument Sans', sans-serif;
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.08em;
color: var(--nb-und-paper);
background: var(--nb-und-ink);
border: none;
padding: 8px 18px;
border-radius: 2px;
cursor: pointer;
transition: background 0.2s;
}
.nb-und .btn-nav:hover {
background: var(--nb-und-red);
}
.nb-und .page {
padding-top: var(--nb-und-nav-h);
}
.nb-und .hero {
padding: 6rem 3rem 4rem;
max-width: 1440px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6rem;
align-items: end;
}
.nb-und .hero-left h1 {
font-family: 'Libre Baskerville', serif;
font-size: clamp(3rem, 5vw, 5.5rem);
font-weight: 700;
letter-spacing: -0.03em;
line-height: 1.08;
color: var(--nb-und-ink);
margin-bottom: 1.5rem;
}
.nb-und .hero-left h1 em {
font-style: italic;
font-weight: 400;
color: var(--nb-und-red);
}
.nb-und .hero-left p {
font-size: 1rem;
color: var(--nb-und-ink-2);
line-height: 1.7;
max-width: 44ch;
}
.nb-und .hero-right {
padding-bottom: 1rem;
}
.nb-und .hero-right p {
font-family: 'Libre Baskerville', serif;
font-size: 1.3rem;
font-style: italic;
font-weight: 400;
color: var(--nb-und-ink-2);
line-height: 1.6;
max-width: 36ch;
}
.nb-und .hero-right p strong {
color: var(--nb-und-ink);
font-style: normal;
font-weight: 700;
}
.nb-und .section-rule {
height: 1px;
background: var(--nb-und-rule);
margin: 0 3rem;
}
.nb-und .section {
padding: 5rem 3rem;
max-width: 1440px;
margin: 0 auto;
}
.nb-und .variant-label {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
}
.nb-und .variant-num {
font-family: 'Libre Baskerville', serif;
font-size: 3rem;
font-weight: 400;
color: var(--nb-und-rule);
line-height: 1;
}
.nb-und .variant-info h3 {
font-size: 0.88rem;
font-weight: 600;
color: var(--nb-und-ink);
letter-spacing: 0.02em;
margin-bottom: 3px;
}
.nb-und .variant-info p {
font-size: 0.78rem;
color: var(--nb-und-ink-2);
line-height: 1.5;
}
.nb-und .demo-nav {
border: 1px solid var(--nb-und-rule);
border-radius: 4px;
background: var(--nb-und-paper);
height: 60px;
display: flex;
align-items: center;
padding: 0 2rem;
position: relative;
margin-bottom: 1.5rem;
overflow: hidden;
}
.nb-und .demo-wrap {
position: relative;
display: flex;
align-items: center;
}
.nb-und .code-note {
font-family: 'Instrument Sans', monospace;
font-size: 0.72rem;
color: var(--nb-und-ink-3);
line-height: 1.7;
background: #f2f0ea;
border-left: 2px solid var(--nb-und-rule);
padding: 0.75rem 1rem;
border-radius: 0 3px 3px 0;
margin-bottom: 3rem;
}
.nb-und .code-note code {
font-family: monospace;
color: var(--nb-und-red);
font-size: 0.7rem;
}
.nb-und .alphabet-demo {
margin: 4rem 3rem;
padding: 3rem;
border: 1px solid var(--nb-und-rule);
border-radius: 4px;
text-align: center;
}
.nb-und .alpha-label {
font-size: 0.62rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--nb-und-ink-3);
margin-bottom: 2rem;
}
.nb-und .alpha-links {
display: flex;
align-items: center;
justify-content: center;
gap: 0;
flex-wrap: wrap;
}
.nb-und .alpha-links a {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
font-family: 'Libre Baskerville', serif;
font-size: 1rem;
color: var(--nb-und-ink-3);
text-decoration: none;
position: relative;
transition: color 0.2s;
border-radius: 4px;
}
.nb-und .alpha-links a::after {
content: '';
position: absolute;
bottom: 4px;
left: 50%;
right: 50%;
height: 1px;
background: var(--nb-und-ink);
transition: left 0.3s ease, right 0.3s ease;
}
.nb-und .alpha-links a:hover {
color: var(--nb-und-ink);
}
.nb-und .alpha-links a:hover::after {
left: 8px;
right: 8px;
}
.nb-und .showcase {
padding: 5rem 3rem;
border-top: 1px solid var(--nb-und-rule);
max-width: 1440px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 0;
}
.nb-und .showcase-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 2rem 0;
border-bottom: 1px solid var(--nb-und-rule);
cursor: pointer;
overflow: hidden;
position: relative;
}
.nb-und .showcase-title {
font-family: 'Libre Baskerville', serif;
font-size: clamp(2rem, 4vw, 4rem);
font-weight: 400;
color: var(--nb-und-ink);
line-height: 1;
position: relative;
display: inline-block;
transition: color 0.2s;
}
.nb-und .showcase-title::after {
content: '';
position: absolute;
bottom: 4px;
left: 0;
right: 100%;
height: 2px;
background: var(--nb-und-red);
transition: right 0.5s cubic-bezier(0.25, 1, 0.5, 1);
}
.nb-und .showcase-item:hover .showcase-title::after {
right: 0;
}
.nb-und .showcase-item:hover .showcase-title {
color: var(--nb-und-red);
}
.nb-und .showcase-right {
display: flex;
align-items: center;
gap: 1rem;
}
.nb-und .showcase-meta {
font-size: 0.72rem;
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--nb-und-ink-3);
}
.nb-und .showcase-arrow {
font-size: 1.5rem;
color: var(--nb-und-ink-3);
transform: translateX(-8px);
transition: transform 0.3s ease, color 0.2s;
}
.nb-und .showcase-item:hover .showcase-arrow {
transform: translateX(0);
color: var(--nb-und-red);
}
@media (max-width: 900px) {
.nb-und .hero {
grid-template-columns: 1fr;
gap: 2rem;
padding: 3rem 1.5rem 2rem;
}
.nb-und .nav-end a,
.nb-und .nav-end-dot {
display: none;
}
.nb-und .section {
padding: 3rem 1.5rem;
}
.nb-und .alphabet-demo {
margin: 2rem 1.5rem;
padding: 2rem 1rem;
}
.nb-und .showcase {
padding: 3rem 1.5rem;
}
.nb-und .demo-nav {
padding: 0 1rem;
}
.nb-und nav .nav-inner {
padding: 0 1.5rem;
}
}
@media (prefers-reduced-motion: reduce) {
.nb-und * {
transition: none !important;
}
}(() => {
// Scoped magnetic-line wiring. Two instances of the magnetic line
// pattern run on the page (the main top nav + the demo nav in
// variant 01). Each is independently bound to its wrapper + nav
// + line + static-line via data-* attributes — no document.getElementById,
// no shared state, multiple instances coexist safely.
const root = document.querySelector('.nb-und');
if (!root) return;
function setupMagneticLine(navEl, wrapper, line, staticLine) {
if (!navEl || !wrapper || !line || !staticLine) return;
const links = navEl.querySelectorAll('a');
function positionLine(el, targetLine, useAccent) {
const wRect = wrapper.getBoundingClientRect();
const rect = el.getBoundingClientRect();
targetLine.style.left = (rect.left - wRect.left) + 'px';
targetLine.style.width = rect.width + 'px';
if (useAccent) targetLine.style.background = 'var(--nb-und-red)';
}
const active = navEl.querySelector('a.active');
if (active) {
requestAnimationFrame(() => positionLine(active, staticLine, true));
}
links.forEach((a) => {
a.addEventListener('mouseenter', () => {
positionLine(a, line);
line.classList.add('visible');
});
a.addEventListener('click', (e) => {
e.preventDefault();
links.forEach((l) => l.classList.remove('active'));
a.classList.add('active');
positionLine(a, staticLine, true);
});
});
wrapper.addEventListener('mouseleave', () => {
line.classList.remove('visible');
});
}
// Main top nav
setupMagneticLine(
root.querySelector('[data-nb-und-nav]'),
root.querySelector('[data-nb-und-wrap]'),
root.querySelector('[data-nb-und-line]'),
root.querySelector('[data-nb-und-static]'),
);
// Demo 1 (the in-page variant showcase)
setupMagneticLine(
root.querySelector('[data-nb-und-demo-nav]'),
root.querySelector('[data-nb-und-demo-wrap]'),
root.querySelector('[data-nb-und-demo-line]'),
root.querySelector('[data-nb-und-demo-static]'),
);
// Variant 2 / 3 click-to-activate (pure CSS hover; this just keeps the
// active state in sync after a click).
root.querySelectorAll('.nav-links-v2 a, .nav-links-v3 a').forEach((a) => {
a.addEventListener('click', (e) => {
e.preventDefault();
const siblings = a.closest('ul').querySelectorAll('a');
siblings.forEach((s) => s.classList.remove('active'));
a.classList.add('active');
});
});
// Alphabet ticker — 26 links built once.
const alpha = root.querySelector('[data-nb-und-alpha]');
if (alpha && alpha.childElementCount === 0) {
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').forEach((ch) => {
const a = document.createElement('a');
a.href = '#';
a.textContent = ch;
a.addEventListener('click', (e) => e.preventDefault());
alpha.appendChild(a);
});
}
})();(() => {
// Scoped magnetic-line wiring. Two instances of the magnetic line
// pattern run on the page (the main top nav + the demo nav in
// variant 01). Each is independently bound to its wrapper + nav
// + line + static-line via data-* attributes — no document.getElementById,
// no shared state, multiple instances coexist safely.
const root = document.querySelector('.nb-und');
if (!root) return;
function setupMagneticLine(navEl, wrapper, line, staticLine) {
if (!navEl || !wrapper || !line || !staticLine) return;
const links = navEl.querySelectorAll('a');
function positionLine(el, targetLine, useAccent) {
const wRect = wrapper.getBoundingClientRect();
const rect = el.getBoundingClientRect();
targetLine.style.left = (rect.left - wRect.left) + 'px';
targetLine.style.width = rect.width + 'px';
if (useAccent) targetLine.style.background = 'var(--nb-und-red)';
}
const active = navEl.querySelector('a.active');
if (active) {
requestAnimationFrame(() => positionLine(active, staticLine, true));
}
links.forEach((a) => {
a.addEventListener('mouseenter', () => {
positionLine(a, line);
line.classList.add('visible');
});
a.addEventListener('click', (e) => {
e.preventDefault();
links.forEach((l) => l.classList.remove('active'));
a.classList.add('active');
positionLine(a, staticLine, true);
});
});
wrapper.addEventListener('mouseleave', () => {
line.classList.remove('visible');
});
}
// Main top nav
setupMagneticLine(
root.querySelector('[data-nb-und-nav]'),
root.querySelector('[data-nb-und-wrap]'),
root.querySelector('[data-nb-und-line]'),
root.querySelector('[data-nb-und-static]'),
);
// Demo 1 (the in-page variant showcase)
setupMagneticLine(
root.querySelector('[data-nb-und-demo-nav]'),
root.querySelector('[data-nb-und-demo-wrap]'),
root.querySelector('[data-nb-und-demo-line]'),
root.querySelector('[data-nb-und-demo-static]'),
);
// Variant 2 / 3 click-to-activate (pure CSS hover; this just keeps the
// active state in sync after a click).
root.querySelectorAll('.nav-links-v2 a, .nav-links-v3 a').forEach((a) => {
a.addEventListener('click', (e) => {
e.preventDefault();
const siblings = a.closest('ul').querySelectorAll('a');
siblings.forEach((s) => s.classList.remove('active'));
a.classList.add('active');
});
});
// Alphabet ticker — 26 links built once.
const alpha = root.querySelector('[data-nb-und-alpha]');
if (alpha && alpha.childElementCount === 0) {
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').forEach((ch) => {
const a = document.createElement('a');
a.href = '#';
a.textContent = ch;
a.addEventListener('click', (e) => e.preventDefault());
alpha.appendChild(a);
});
}
})();Here's a working CSS Navbar Design from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Navbar with Underline Hover Animation
Source: https://codefronts.com/navigation/css-navbars/css-navbar-with-underline-hover-animation/
A minimalist CSS navbar with three distinct underline hover animation techniques side-by-side — proving that the most elegant nav effects come from doing less, not more. Variant 1 (the production nav at top) uses a JS-driven magnetic ink line: a single absolutely-positioned 1.5px rail glides between hovered items via getBoundingClientRect, with a red static line marking the active item. Variant 2 is pure CSS — each link owns an `::after` pseudo-element that starts at `left: 50%; right: 50%` (zero width, centered) and expands outward to the padding edge on hover, blooming symmetrically. Variant 3 uses the same `::after` trick but transitions only one edge, producing a left-anchored directional wipe. The page includes a Libre Baskerville serif "art of the quiet hover" hero, an A–Z alphabet ticker where every letter gets the underline treatment, and a 4-row showcase list where each title gets a 2px red underline wipe on hover. Libre Baskerville + Instrument Sans. Best for editorial sites, personal portfolios, design studio navs, content-led blogs, minimalist typography-first layouts.
## HTML
```html
<section class="nb-und" aria-label="CSS navbar with underline hover animation demo">
<nav aria-label="Primary">
<div class="nav-inner">
<a href="#" class="brand">Thesis</a>
<div class="nav-links-wrap" data-nb-und-wrap>
<div class="ink-line" data-nb-und-line aria-hidden="true"></div>
<div class="ink-line-static" data-nb-und-static aria-hidden="true"></div>
<ul class="nav-links" data-nb-und-nav>
<li><a href="#" class="active"><span>Studio</span></a></li>
<li><a href="#"><span>Work</span></a></li>
<li><a href="#"><span>Writing</span></a></li>
<li><a href="#"><span>Process</span></a></li>
<li><a href="#"><span>Archive</span></a></li>
<li><a href="#"><span>Contact</span></a></li>
</ul>
</div>
<div class="nav-spacer"></div>
<div class="nav-end">
<a href="#">Index</a>
<div class="nav-end-dot" aria-hidden="true"></div>
<a href="#">RSS</a>
<div class="nav-end-dot" aria-hidden="true"></div>
<button class="btn-nav" type="button">Inquire</button>
</div>
</div>
</nav>
<div class="page">
<div class="hero">
<div class="hero-left">
<h1>The art of<br>the <em>quiet</em><br>hover.</h1>
<p>Three distinct CSS underline animation techniques — each achieving elegance through restraint. The navbar above uses a magnetic sliding ink line that follows your cursor. Below, explore every method.</p>
</div>
<div class="hero-right">
<p>"Typography is a <strong>silent conversation</strong> — the best underlines join it without interrupting. Hover the navigation above and each demo below to see them in motion."</p>
</div>
</div>
<div class="section-rule"></div>
<section class="section">
<div class="variant-label">
<span class="variant-num">01</span>
<div class="variant-info">
<h3>Magnetic Sliding Line — Single Global Rail</h3>
<p>One line glides beneath the entire nav, tracking the hovered link. Active item uses a static accent-colored line.</p>
</div>
</div>
<div class="demo-nav">
<div class="nav-links-wrap demo-wrap" data-nb-und-demo-wrap>
<div class="ink-line" data-nb-und-demo-line aria-hidden="true"></div>
<div class="ink-line-static" data-nb-und-demo-static aria-hidden="true"></div>
<ul class="nav-links" data-nb-und-demo-nav>
<li><a href="#" class="active"><span>Objects</span></a></li>
<li><a href="#"><span>Spaces</span></a></li>
<li><a href="#"><span>Systems</span></a></li>
<li><a href="#"><span>Texts</span></a></li>
<li><a href="#"><span>People</span></a></li>
</ul>
</div>
</div>
<p class="code-note">
One <code>.ink-line</code> element is positioned with JS using <code>getBoundingClientRect()</code> on each hover target.
CSS <code>transition: left, width</code> with <code>cubic-bezier(0.25,1,0.5,1)</code> creates the magnetic feel.
The static <code>.ink-line-static</code> marks the active item in accent red.
</p>
</section>
<div class="section-rule"></div>
<section class="section">
<div class="variant-label">
<span class="variant-num">02</span>
<div class="variant-info">
<h3>Center-Bloom — Per-Link <code>::after</code> Pseudo-Element</h3>
<p>Each link owns its underline. On hover, it expands from the center outward — no JavaScript needed at all.</p>
</div>
</div>
<div class="demo-nav">
<ul class="nav-links-v2">
<li><a href="#" class="active">Foundations</a></li>
<li><a href="#">Components</a></li>
<li><a href="#">Patterns</a></li>
<li><a href="#">Motion</a></li>
<li><a href="#">Tokens</a></li>
</ul>
</div>
<p class="code-note">
CSS only. Each <code>a::after</code> starts at <code>left: 50%; right: 50%</code> (zero width, centered).
On <code>:hover</code>, <code>left</code> and <code>right</code> both animate to the padding value — blooming outward symmetrically.
</p>
</section>
<div class="section-rule"></div>
<section class="section">
<div class="variant-label">
<span class="variant-num">03</span>
<div class="variant-info">
<h3>Left-Wipe — Directional Reveal</h3>
<p>The underline sweeps in from the left, anchored to the link's left edge. Clean, directional, editorial.</p>
</div>
</div>
<div class="demo-nav">
<ul class="nav-links-v3">
<li><a href="#" class="active">Editorial</a></li>
<li><a href="#">Research</a></li>
<li><a href="#">Narrative</a></li>
<li><a href="#">Margins</a></li>
<li><a href="#">Colophon</a></li>
</ul>
</div>
<p class="code-note">
CSS only. <code>a::before</code> starts at <code>left: 50%; right: 50%</code> and transitions to <code>left: 1.1rem; right: 1.1rem</code>.
The asymmetric easing of <code>left</code> gives it the wipe directionality.
</p>
</section>
<div class="section-rule"></div>
<div class="alphabet-demo">
<p class="alpha-label">Every link, same principle — hover any letter</p>
<div class="alpha-links" data-nb-und-alpha></div>
</div>
<div class="section-rule"></div>
<div class="showcase">
<div class="showcase-item">
<span class="showcase-title">Clarity</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 01</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
<div class="showcase-item">
<span class="showcase-title">Restraint</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 02</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
<div class="showcase-item">
<span class="showcase-title">Intention</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 03</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
<div class="showcase-item">
<span class="showcase-title">Motion</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 04</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
</div>
</div>
</section>
```
## CSS
```css
/* ─── 08 Thesis — CSS navbar underline hover animation UI ─────── */
@import url('https://fonts.googleapis.com/css2?family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=Instrument+Sans:ital,wght@0,400;0,500;0,600;1,400&display=swap');
.nb-und {
--nb-und-paper: #f8f6f1;
--nb-und-ink: #1a1916;
--nb-und-ink-2: #6b6760;
--nb-und-ink-3: #b5b2ac;
--nb-und-rule: #e0ddd6;
--nb-und-red: #c93a24;
--nb-und-nav-h: 70px;
position: relative;
width: 100%;
min-height: 1600px;
background: var(--nb-und-paper);
color: var(--nb-und-ink);
font-family: 'Instrument Sans', sans-serif;
overflow: clip;
box-sizing: border-box;
}
.nb-und *,
.nb-und *::before,
.nb-und *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.nb-und nav {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 100;
height: var(--nb-und-nav-h);
background: var(--nb-und-paper);
border-bottom: 1px solid var(--nb-und-rule);
}
.nb-und .nav-inner {
max-width: 1440px;
margin: 0 auto;
height: 100%;
display: flex;
align-items: center;
padding: 0 3rem;
}
.nb-und .brand {
font-family: 'Libre Baskerville', serif;
font-size: 1.2rem;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--nb-und-ink);
text-decoration: none;
flex-shrink: 0;
margin-right: 4rem;
}
.nb-und .nav-links-wrap {
flex: 1;
position: relative;
display: flex;
align-items: center;
}
.nb-und .nav-links {
display: flex;
align-items: center;
list-style: none;
gap: 0;
}
.nb-und .ink-line {
position: absolute;
bottom: -1px;
height: 1.5px;
background: var(--nb-und-ink);
transition: left 0.38s cubic-bezier(0.25, 1, 0.5, 1), width 0.38s cubic-bezier(0.25, 1, 0.5, 1), opacity 0.25s;
pointer-events: none;
opacity: 0;
}
.nb-und .ink-line.visible {
opacity: 1;
}
.nb-und .ink-line-static {
position: absolute;
bottom: -1px;
height: 1.5px;
background: var(--nb-und-red);
pointer-events: none;
transition: left 0.4s cubic-bezier(0.25, 1, 0.5, 1), width 0.4s;
}
.nb-und .nav-links a {
display: flex;
align-items: center;
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
text-decoration: none;
padding: 0 1.1rem;
height: var(--nb-und-nav-h);
transition: color 0.22s;
position: relative;
white-space: nowrap;
}
.nb-und .nav-links a:hover {
color: var(--nb-und-ink);
}
.nb-und .nav-links a.active {
color: var(--nb-und-ink);
}
.nb-und .nav-links a span {
display: inline-block;
transition: letter-spacing 0.35s cubic-bezier(0.25, 1, 0.5, 1);
}
.nb-und .nav-links a:hover span {
letter-spacing: 0.09em;
}
.nb-und .nav-links a.active span {
letter-spacing: 0.02em;
}
/* Variant 2 — center-bloom (pure CSS) */
.nb-und .nav-links-v2 {
display: flex;
align-items: center;
list-style: none;
gap: 0;
}
.nb-und .nav-links-v2 a {
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
text-decoration: none;
padding: 0 1.1rem;
height: var(--nb-und-nav-h);
position: relative;
white-space: nowrap;
transition: color 0.22s;
}
.nb-und .nav-links-v2 a:hover {
color: var(--nb-und-ink);
}
.nb-und .nav-links-v2 a.active {
color: var(--nb-und-ink);
}
.nb-und .nav-links-v2 a::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
right: 50%;
height: 1.5px;
background: var(--nb-und-ink);
transition: left 0.35s cubic-bezier(0.25, 1, 0.5, 1), right 0.35s cubic-bezier(0.25, 1, 0.5, 1);
}
.nb-und .nav-links-v2 a.active::after {
background: var(--nb-und-red);
left: 1.1rem;
right: 1.1rem;
}
.nb-und .nav-links-v2 a:hover::after {
left: 1.1rem;
right: 1.1rem;
}
/* Variant 3 — left-wipe */
.nb-und .nav-links-v3 {
display: flex;
align-items: center;
list-style: none;
gap: 0;
}
.nb-und .nav-links-v3 a {
display: flex;
align-items: center;
height: var(--nb-und-nav-h);
padding: 0 1.1rem;
text-decoration: none;
white-space: nowrap;
position: relative;
overflow: hidden;
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
transition: color 0.22s;
}
.nb-und .nav-links-v3 a:hover,
.nb-und .nav-links-v3 a.active {
color: var(--nb-und-ink);
}
.nb-und .nav-links-v3 a::before {
content: '';
position: absolute;
bottom: 0;
left: 50%;
right: 50%;
height: 1.5px;
background: var(--nb-und-ink);
transition: left 0.3s ease, right 0.3s ease;
}
.nb-und .nav-links-v3 a:hover::before {
left: 1.1rem;
right: 1.1rem;
}
.nb-und .nav-links-v3 a.active::before {
left: 1.1rem;
right: 1.1rem;
background: var(--nb-und-red);
}
.nb-und .nav-spacer {
flex: 1;
}
.nb-und .nav-end {
display: flex;
align-items: center;
gap: 1.5rem;
flex-shrink: 0;
}
.nb-und .nav-end a {
font-size: 0.78rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
text-decoration: none;
transition: color 0.2s;
position: relative;
}
.nb-und .nav-end a::after {
content: '';
position: absolute;
bottom: -2px;
left: 50%;
right: 50%;
height: 1px;
background: var(--nb-und-ink);
transition: left 0.3s ease, right 0.3s ease;
}
.nb-und .nav-end a:hover {
color: var(--nb-und-ink);
}
.nb-und .nav-end a:hover::after {
left: 0;
right: 0;
}
.nb-und .nav-end-dot {
width: 3px;
height: 3px;
border-radius: 50%;
background: var(--nb-und-rule);
}
.nb-und .btn-nav {
font-family: 'Instrument Sans', sans-serif;
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.08em;
color: var(--nb-und-paper);
background: var(--nb-und-ink);
border: none;
padding: 8px 18px;
border-radius: 2px;
cursor: pointer;
transition: background 0.2s;
}
.nb-und .btn-nav:hover {
background: var(--nb-und-red);
}
.nb-und .page {
padding-top: var(--nb-und-nav-h);
}
.nb-und .hero {
padding: 6rem 3rem 4rem;
max-width: 1440px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6rem;
align-items: end;
}
.nb-und .hero-left h1 {
font-family: 'Libre Baskerville', serif;
font-size: clamp(3rem, 5vw, 5.5rem);
font-weight: 700;
letter-spacing: -0.03em;
line-height: 1.08;
color: var(--nb-und-ink);
margin-bottom: 1.5rem;
}
.nb-und .hero-left h1 em {
font-style: italic;
font-weight: 400;
color: var(--nb-und-red);
}
.nb-und .hero-left p {
font-size: 1rem;
color: var(--nb-und-ink-2);
line-height: 1.7;
max-width: 44ch;
}
.nb-und .hero-right {
padding-bottom: 1rem;
}
.nb-und .hero-right p {
font-family: 'Libre Baskerville', serif;
font-size: 1.3rem;
font-style: italic;
font-weight: 400;
color: var(--nb-und-ink-2);
line-height: 1.6;
max-width: 36ch;
}
.nb-und .hero-right p strong {
color: var(--nb-und-ink);
font-style: normal;
font-weight: 700;
}
.nb-und .section-rule {
height: 1px;
background: var(--nb-und-rule);
margin: 0 3rem;
}
.nb-und .section {
padding: 5rem 3rem;
max-width: 1440px;
margin: 0 auto;
}
.nb-und .variant-label {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
}
.nb-und .variant-num {
font-family: 'Libre Baskerville', serif;
font-size: 3rem;
font-weight: 400;
color: var(--nb-und-rule);
line-height: 1;
}
.nb-und .variant-info h3 {
font-size: 0.88rem;
font-weight: 600;
color: var(--nb-und-ink);
letter-spacing: 0.02em;
margin-bottom: 3px;
}
.nb-und .variant-info p {
font-size: 0.78rem;
color: var(--nb-und-ink-2);
line-height: 1.5;
}
.nb-und .demo-nav {
border: 1px solid var(--nb-und-rule);
border-radius: 4px;
background: var(--nb-und-paper);
height: 60px;
display: flex;
align-items: center;
padding: 0 2rem;
position: relative;
margin-bottom: 1.5rem;
overflow: hidden;
}
.nb-und .demo-wrap {
position: relative;
display: flex;
align-items: center;
}
.nb-und .code-note {
font-family: 'Instrument Sans', monospace;
font-size: 0.72rem;
color: var(--nb-und-ink-3);
line-height: 1.7;
background: #f2f0ea;
border-left: 2px solid var(--nb-und-rule);
padding: 0.75rem 1rem;
border-radius: 0 3px 3px 0;
margin-bottom: 3rem;
}
.nb-und .code-note code {
font-family: monospace;
color: var(--nb-und-red);
font-size: 0.7rem;
}
.nb-und .alphabet-demo {
margin: 4rem 3rem;
padding: 3rem;
border: 1px solid var(--nb-und-rule);
border-radius: 4px;
text-align: center;
}
.nb-und .alpha-label {
font-size: 0.62rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--nb-und-ink-3);
margin-bottom: 2rem;
}
.nb-und .alpha-links {
display: flex;
align-items: center;
justify-content: center;
gap: 0;
flex-wrap: wrap;
}
.nb-und .alpha-links a {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
font-family: 'Libre Baskerville', serif;
font-size: 1rem;
color: var(--nb-und-ink-3);
text-decoration: none;
position: relative;
transition: color 0.2s;
border-radius: 4px;
}
.nb-und .alpha-links a::after {
content: '';
position: absolute;
bottom: 4px;
left: 50%;
right: 50%;
height: 1px;
background: var(--nb-und-ink);
transition: left 0.3s ease, right 0.3s ease;
}
.nb-und .alpha-links a:hover {
color: var(--nb-und-ink);
}
.nb-und .alpha-links a:hover::after {
left: 8px;
right: 8px;
}
.nb-und .showcase {
padding: 5rem 3rem;
border-top: 1px solid var(--nb-und-rule);
max-width: 1440px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 0;
}
.nb-und .showcase-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 2rem 0;
border-bottom: 1px solid var(--nb-und-rule);
cursor: pointer;
overflow: hidden;
position: relative;
}
.nb-und .showcase-title {
font-family: 'Libre Baskerville', serif;
font-size: clamp(2rem, 4vw, 4rem);
font-weight: 400;
color: var(--nb-und-ink);
line-height: 1;
position: relative;
display: inline-block;
transition: color 0.2s;
}
.nb-und .showcase-title::after {
content: '';
position: absolute;
bottom: 4px;
left: 0;
right: 100%;
height: 2px;
background: var(--nb-und-red);
transition: right 0.5s cubic-bezier(0.25, 1, 0.5, 1);
}
.nb-und .showcase-item:hover .showcase-title::after {
right: 0;
}
.nb-und .showcase-item:hover .showcase-title {
color: var(--nb-und-red);
}
.nb-und .showcase-right {
display: flex;
align-items: center;
gap: 1rem;
}
.nb-und .showcase-meta {
font-size: 0.72rem;
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--nb-und-ink-3);
}
.nb-und .showcase-arrow {
font-size: 1.5rem;
color: var(--nb-und-ink-3);
transform: translateX(-8px);
transition: transform 0.3s ease, color 0.2s;
}
.nb-und .showcase-item:hover .showcase-arrow {
transform: translateX(0);
color: var(--nb-und-red);
}
@media (max-width: 900px) {
.nb-und .hero {
grid-template-columns: 1fr;
gap: 2rem;
padding: 3rem 1.5rem 2rem;
}
.nb-und .nav-end a,
.nb-und .nav-end-dot {
display: none;
}
.nb-und .section {
padding: 3rem 1.5rem;
}
.nb-und .alphabet-demo {
margin: 2rem 1.5rem;
padding: 2rem 1rem;
}
.nb-und .showcase {
padding: 3rem 1.5rem;
}
.nb-und .demo-nav {
padding: 0 1rem;
}
.nb-und nav .nav-inner {
padding: 0 1.5rem;
}
}
@media (prefers-reduced-motion: reduce) {
.nb-und * {
transition: none !important;
}
}
```
## JavaScript
```js
(() => {
// Scoped magnetic-line wiring. Two instances of the magnetic line
// pattern run on the page (the main top nav + the demo nav in
// variant 01). Each is independently bound to its wrapper + nav
// + line + static-line via data-* attributes — no document.getElementById,
// no shared state, multiple instances coexist safely.
const root = document.querySelector('.nb-und');
if (!root) return;
function setupMagneticLine(navEl, wrapper, line, staticLine) {
if (!navEl || !wrapper || !line || !staticLine) return;
const links = navEl.querySelectorAll('a');
function positionLine(el, targetLine, useAccent) {
const wRect = wrapper.getBoundingClientRect();
const rect = el.getBoundingClientRect();
targetLine.style.left = (rect.left - wRect.left) + 'px';
targetLine.style.width = rect.width + 'px';
if (useAccent) targetLine.style.background = 'var(--nb-und-red)';
}
const active = navEl.querySelector('a.active');
if (active) {
requestAnimationFrame(() => positionLine(active, staticLine, true));
}
links.forEach((a) => {
a.addEventListener('mouseenter', () => {
positionLine(a, line);
line.classList.add('visible');
});
a.addEventListener('click', (e) => {
e.preventDefault();
links.forEach((l) => l.classList.remove('active'));
a.classList.add('active');
positionLine(a, staticLine, true);
});
});
wrapper.addEventListener('mouseleave', () => {
line.classList.remove('visible');
});
}
// Main top nav
setupMagneticLine(
root.querySelector('[data-nb-und-nav]'),
root.querySelector('[data-nb-und-wrap]'),
root.querySelector('[data-nb-und-line]'),
root.querySelector('[data-nb-und-static]'),
);
// Demo 1 (the in-page variant showcase)
setupMagneticLine(
root.querySelector('[data-nb-und-demo-nav]'),
root.querySelector('[data-nb-und-demo-wrap]'),
root.querySelector('[data-nb-und-demo-line]'),
root.querySelector('[data-nb-und-demo-static]'),
);
// Variant 2 / 3 click-to-activate (pure CSS hover; this just keeps the
// active state in sync after a click).
root.querySelectorAll('.nav-links-v2 a, .nav-links-v3 a').forEach((a) => {
a.addEventListener('click', (e) => {
e.preventDefault();
const siblings = a.closest('ul').querySelectorAll('a');
siblings.forEach((s) => s.classList.remove('active'));
a.classList.add('active');
});
});
// Alphabet ticker — 26 links built once.
const alpha = root.querySelector('[data-nb-und-alpha]');
if (alpha && alpha.childElementCount === 0) {
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').forEach((ch) => {
const a = document.createElement('a');
a.href = '#';
a.textContent = ch;
a.addEventListener('click', (e) => e.preventDefault());
alpha.appendChild(a);
});
}
})();
```Here's a working CSS Navbar Design from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Navbar with Underline Hover Animation
Source: https://codefronts.com/navigation/css-navbars/css-navbar-with-underline-hover-animation/
A minimalist CSS navbar with three distinct underline hover animation techniques side-by-side — proving that the most elegant nav effects come from doing less, not more. Variant 1 (the production nav at top) uses a JS-driven magnetic ink line: a single absolutely-positioned 1.5px rail glides between hovered items via getBoundingClientRect, with a red static line marking the active item. Variant 2 is pure CSS — each link owns an `::after` pseudo-element that starts at `left: 50%; right: 50%` (zero width, centered) and expands outward to the padding edge on hover, blooming symmetrically. Variant 3 uses the same `::after` trick but transitions only one edge, producing a left-anchored directional wipe. The page includes a Libre Baskerville serif "art of the quiet hover" hero, an A–Z alphabet ticker where every letter gets the underline treatment, and a 4-row showcase list where each title gets a 2px red underline wipe on hover. Libre Baskerville + Instrument Sans. Best for editorial sites, personal portfolios, design studio navs, content-led blogs, minimalist typography-first layouts.
## HTML
```html
<section class="nb-und" aria-label="CSS navbar with underline hover animation demo">
<nav aria-label="Primary">
<div class="nav-inner">
<a href="#" class="brand">Thesis</a>
<div class="nav-links-wrap" data-nb-und-wrap>
<div class="ink-line" data-nb-und-line aria-hidden="true"></div>
<div class="ink-line-static" data-nb-und-static aria-hidden="true"></div>
<ul class="nav-links" data-nb-und-nav>
<li><a href="#" class="active"><span>Studio</span></a></li>
<li><a href="#"><span>Work</span></a></li>
<li><a href="#"><span>Writing</span></a></li>
<li><a href="#"><span>Process</span></a></li>
<li><a href="#"><span>Archive</span></a></li>
<li><a href="#"><span>Contact</span></a></li>
</ul>
</div>
<div class="nav-spacer"></div>
<div class="nav-end">
<a href="#">Index</a>
<div class="nav-end-dot" aria-hidden="true"></div>
<a href="#">RSS</a>
<div class="nav-end-dot" aria-hidden="true"></div>
<button class="btn-nav" type="button">Inquire</button>
</div>
</div>
</nav>
<div class="page">
<div class="hero">
<div class="hero-left">
<h1>The art of<br>the <em>quiet</em><br>hover.</h1>
<p>Three distinct CSS underline animation techniques — each achieving elegance through restraint. The navbar above uses a magnetic sliding ink line that follows your cursor. Below, explore every method.</p>
</div>
<div class="hero-right">
<p>"Typography is a <strong>silent conversation</strong> — the best underlines join it without interrupting. Hover the navigation above and each demo below to see them in motion."</p>
</div>
</div>
<div class="section-rule"></div>
<section class="section">
<div class="variant-label">
<span class="variant-num">01</span>
<div class="variant-info">
<h3>Magnetic Sliding Line — Single Global Rail</h3>
<p>One line glides beneath the entire nav, tracking the hovered link. Active item uses a static accent-colored line.</p>
</div>
</div>
<div class="demo-nav">
<div class="nav-links-wrap demo-wrap" data-nb-und-demo-wrap>
<div class="ink-line" data-nb-und-demo-line aria-hidden="true"></div>
<div class="ink-line-static" data-nb-und-demo-static aria-hidden="true"></div>
<ul class="nav-links" data-nb-und-demo-nav>
<li><a href="#" class="active"><span>Objects</span></a></li>
<li><a href="#"><span>Spaces</span></a></li>
<li><a href="#"><span>Systems</span></a></li>
<li><a href="#"><span>Texts</span></a></li>
<li><a href="#"><span>People</span></a></li>
</ul>
</div>
</div>
<p class="code-note">
One <code>.ink-line</code> element is positioned with JS using <code>getBoundingClientRect()</code> on each hover target.
CSS <code>transition: left, width</code> with <code>cubic-bezier(0.25,1,0.5,1)</code> creates the magnetic feel.
The static <code>.ink-line-static</code> marks the active item in accent red.
</p>
</section>
<div class="section-rule"></div>
<section class="section">
<div class="variant-label">
<span class="variant-num">02</span>
<div class="variant-info">
<h3>Center-Bloom — Per-Link <code>::after</code> Pseudo-Element</h3>
<p>Each link owns its underline. On hover, it expands from the center outward — no JavaScript needed at all.</p>
</div>
</div>
<div class="demo-nav">
<ul class="nav-links-v2">
<li><a href="#" class="active">Foundations</a></li>
<li><a href="#">Components</a></li>
<li><a href="#">Patterns</a></li>
<li><a href="#">Motion</a></li>
<li><a href="#">Tokens</a></li>
</ul>
</div>
<p class="code-note">
CSS only. Each <code>a::after</code> starts at <code>left: 50%; right: 50%</code> (zero width, centered).
On <code>:hover</code>, <code>left</code> and <code>right</code> both animate to the padding value — blooming outward symmetrically.
</p>
</section>
<div class="section-rule"></div>
<section class="section">
<div class="variant-label">
<span class="variant-num">03</span>
<div class="variant-info">
<h3>Left-Wipe — Directional Reveal</h3>
<p>The underline sweeps in from the left, anchored to the link's left edge. Clean, directional, editorial.</p>
</div>
</div>
<div class="demo-nav">
<ul class="nav-links-v3">
<li><a href="#" class="active">Editorial</a></li>
<li><a href="#">Research</a></li>
<li><a href="#">Narrative</a></li>
<li><a href="#">Margins</a></li>
<li><a href="#">Colophon</a></li>
</ul>
</div>
<p class="code-note">
CSS only. <code>a::before</code> starts at <code>left: 50%; right: 50%</code> and transitions to <code>left: 1.1rem; right: 1.1rem</code>.
The asymmetric easing of <code>left</code> gives it the wipe directionality.
</p>
</section>
<div class="section-rule"></div>
<div class="alphabet-demo">
<p class="alpha-label">Every link, same principle — hover any letter</p>
<div class="alpha-links" data-nb-und-alpha></div>
</div>
<div class="section-rule"></div>
<div class="showcase">
<div class="showcase-item">
<span class="showcase-title">Clarity</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 01</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
<div class="showcase-item">
<span class="showcase-title">Restraint</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 02</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
<div class="showcase-item">
<span class="showcase-title">Intention</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 03</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
<div class="showcase-item">
<span class="showcase-title">Motion</span>
<div class="showcase-right">
<span class="showcase-meta">Principle 04</span>
<span class="showcase-arrow" aria-hidden="true">→</span>
</div>
</div>
</div>
</div>
</section>
```
## CSS
```css
/* ─── 08 Thesis — CSS navbar underline hover animation UI ─────── */
@import url('https://fonts.googleapis.com/css2?family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=Instrument+Sans:ital,wght@0,400;0,500;0,600;1,400&display=swap');
.nb-und {
--nb-und-paper: #f8f6f1;
--nb-und-ink: #1a1916;
--nb-und-ink-2: #6b6760;
--nb-und-ink-3: #b5b2ac;
--nb-und-rule: #e0ddd6;
--nb-und-red: #c93a24;
--nb-und-nav-h: 70px;
position: relative;
width: 100%;
min-height: 1600px;
background: var(--nb-und-paper);
color: var(--nb-und-ink);
font-family: 'Instrument Sans', sans-serif;
overflow: clip;
box-sizing: border-box;
}
.nb-und *,
.nb-und *::before,
.nb-und *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.nb-und nav {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 100;
height: var(--nb-und-nav-h);
background: var(--nb-und-paper);
border-bottom: 1px solid var(--nb-und-rule);
}
.nb-und .nav-inner {
max-width: 1440px;
margin: 0 auto;
height: 100%;
display: flex;
align-items: center;
padding: 0 3rem;
}
.nb-und .brand {
font-family: 'Libre Baskerville', serif;
font-size: 1.2rem;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--nb-und-ink);
text-decoration: none;
flex-shrink: 0;
margin-right: 4rem;
}
.nb-und .nav-links-wrap {
flex: 1;
position: relative;
display: flex;
align-items: center;
}
.nb-und .nav-links {
display: flex;
align-items: center;
list-style: none;
gap: 0;
}
.nb-und .ink-line {
position: absolute;
bottom: -1px;
height: 1.5px;
background: var(--nb-und-ink);
transition: left 0.38s cubic-bezier(0.25, 1, 0.5, 1), width 0.38s cubic-bezier(0.25, 1, 0.5, 1), opacity 0.25s;
pointer-events: none;
opacity: 0;
}
.nb-und .ink-line.visible {
opacity: 1;
}
.nb-und .ink-line-static {
position: absolute;
bottom: -1px;
height: 1.5px;
background: var(--nb-und-red);
pointer-events: none;
transition: left 0.4s cubic-bezier(0.25, 1, 0.5, 1), width 0.4s;
}
.nb-und .nav-links a {
display: flex;
align-items: center;
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
text-decoration: none;
padding: 0 1.1rem;
height: var(--nb-und-nav-h);
transition: color 0.22s;
position: relative;
white-space: nowrap;
}
.nb-und .nav-links a:hover {
color: var(--nb-und-ink);
}
.nb-und .nav-links a.active {
color: var(--nb-und-ink);
}
.nb-und .nav-links a span {
display: inline-block;
transition: letter-spacing 0.35s cubic-bezier(0.25, 1, 0.5, 1);
}
.nb-und .nav-links a:hover span {
letter-spacing: 0.09em;
}
.nb-und .nav-links a.active span {
letter-spacing: 0.02em;
}
/* Variant 2 — center-bloom (pure CSS) */
.nb-und .nav-links-v2 {
display: flex;
align-items: center;
list-style: none;
gap: 0;
}
.nb-und .nav-links-v2 a {
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
text-decoration: none;
padding: 0 1.1rem;
height: var(--nb-und-nav-h);
position: relative;
white-space: nowrap;
transition: color 0.22s;
}
.nb-und .nav-links-v2 a:hover {
color: var(--nb-und-ink);
}
.nb-und .nav-links-v2 a.active {
color: var(--nb-und-ink);
}
.nb-und .nav-links-v2 a::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
right: 50%;
height: 1.5px;
background: var(--nb-und-ink);
transition: left 0.35s cubic-bezier(0.25, 1, 0.5, 1), right 0.35s cubic-bezier(0.25, 1, 0.5, 1);
}
.nb-und .nav-links-v2 a.active::after {
background: var(--nb-und-red);
left: 1.1rem;
right: 1.1rem;
}
.nb-und .nav-links-v2 a:hover::after {
left: 1.1rem;
right: 1.1rem;
}
/* Variant 3 — left-wipe */
.nb-und .nav-links-v3 {
display: flex;
align-items: center;
list-style: none;
gap: 0;
}
.nb-und .nav-links-v3 a {
display: flex;
align-items: center;
height: var(--nb-und-nav-h);
padding: 0 1.1rem;
text-decoration: none;
white-space: nowrap;
position: relative;
overflow: hidden;
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
transition: color 0.22s;
}
.nb-und .nav-links-v3 a:hover,
.nb-und .nav-links-v3 a.active {
color: var(--nb-und-ink);
}
.nb-und .nav-links-v3 a::before {
content: '';
position: absolute;
bottom: 0;
left: 50%;
right: 50%;
height: 1.5px;
background: var(--nb-und-ink);
transition: left 0.3s ease, right 0.3s ease;
}
.nb-und .nav-links-v3 a:hover::before {
left: 1.1rem;
right: 1.1rem;
}
.nb-und .nav-links-v3 a.active::before {
left: 1.1rem;
right: 1.1rem;
background: var(--nb-und-red);
}
.nb-und .nav-spacer {
flex: 1;
}
.nb-und .nav-end {
display: flex;
align-items: center;
gap: 1.5rem;
flex-shrink: 0;
}
.nb-und .nav-end a {
font-size: 0.78rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--nb-und-ink-2);
text-decoration: none;
transition: color 0.2s;
position: relative;
}
.nb-und .nav-end a::after {
content: '';
position: absolute;
bottom: -2px;
left: 50%;
right: 50%;
height: 1px;
background: var(--nb-und-ink);
transition: left 0.3s ease, right 0.3s ease;
}
.nb-und .nav-end a:hover {
color: var(--nb-und-ink);
}
.nb-und .nav-end a:hover::after {
left: 0;
right: 0;
}
.nb-und .nav-end-dot {
width: 3px;
height: 3px;
border-radius: 50%;
background: var(--nb-und-rule);
}
.nb-und .btn-nav {
font-family: 'Instrument Sans', sans-serif;
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.08em;
color: var(--nb-und-paper);
background: var(--nb-und-ink);
border: none;
padding: 8px 18px;
border-radius: 2px;
cursor: pointer;
transition: background 0.2s;
}
.nb-und .btn-nav:hover {
background: var(--nb-und-red);
}
.nb-und .page {
padding-top: var(--nb-und-nav-h);
}
.nb-und .hero {
padding: 6rem 3rem 4rem;
max-width: 1440px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6rem;
align-items: end;
}
.nb-und .hero-left h1 {
font-family: 'Libre Baskerville', serif;
font-size: clamp(3rem, 5vw, 5.5rem);
font-weight: 700;
letter-spacing: -0.03em;
line-height: 1.08;
color: var(--nb-und-ink);
margin-bottom: 1.5rem;
}
.nb-und .hero-left h1 em {
font-style: italic;
font-weight: 400;
color: var(--nb-und-red);
}
.nb-und .hero-left p {
font-size: 1rem;
color: var(--nb-und-ink-2);
line-height: 1.7;
max-width: 44ch;
}
.nb-und .hero-right {
padding-bottom: 1rem;
}
.nb-und .hero-right p {
font-family: 'Libre Baskerville', serif;
font-size: 1.3rem;
font-style: italic;
font-weight: 400;
color: var(--nb-und-ink-2);
line-height: 1.6;
max-width: 36ch;
}
.nb-und .hero-right p strong {
color: var(--nb-und-ink);
font-style: normal;
font-weight: 700;
}
.nb-und .section-rule {
height: 1px;
background: var(--nb-und-rule);
margin: 0 3rem;
}
.nb-und .section {
padding: 5rem 3rem;
max-width: 1440px;
margin: 0 auto;
}
.nb-und .variant-label {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
}
.nb-und .variant-num {
font-family: 'Libre Baskerville', serif;
font-size: 3rem;
font-weight: 400;
color: var(--nb-und-rule);
line-height: 1;
}
.nb-und .variant-info h3 {
font-size: 0.88rem;
font-weight: 600;
color: var(--nb-und-ink);
letter-spacing: 0.02em;
margin-bottom: 3px;
}
.nb-und .variant-info p {
font-size: 0.78rem;
color: var(--nb-und-ink-2);
line-height: 1.5;
}
.nb-und .demo-nav {
border: 1px solid var(--nb-und-rule);
border-radius: 4px;
background: var(--nb-und-paper);
height: 60px;
display: flex;
align-items: center;
padding: 0 2rem;
position: relative;
margin-bottom: 1.5rem;
overflow: hidden;
}
.nb-und .demo-wrap {
position: relative;
display: flex;
align-items: center;
}
.nb-und .code-note {
font-family: 'Instrument Sans', monospace;
font-size: 0.72rem;
color: var(--nb-und-ink-3);
line-height: 1.7;
background: #f2f0ea;
border-left: 2px solid var(--nb-und-rule);
padding: 0.75rem 1rem;
border-radius: 0 3px 3px 0;
margin-bottom: 3rem;
}
.nb-und .code-note code {
font-family: monospace;
color: var(--nb-und-red);
font-size: 0.7rem;
}
.nb-und .alphabet-demo {
margin: 4rem 3rem;
padding: 3rem;
border: 1px solid var(--nb-und-rule);
border-radius: 4px;
text-align: center;
}
.nb-und .alpha-label {
font-size: 0.62rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--nb-und-ink-3);
margin-bottom: 2rem;
}
.nb-und .alpha-links {
display: flex;
align-items: center;
justify-content: center;
gap: 0;
flex-wrap: wrap;
}
.nb-und .alpha-links a {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
font-family: 'Libre Baskerville', serif;
font-size: 1rem;
color: var(--nb-und-ink-3);
text-decoration: none;
position: relative;
transition: color 0.2s;
border-radius: 4px;
}
.nb-und .alpha-links a::after {
content: '';
position: absolute;
bottom: 4px;
left: 50%;
right: 50%;
height: 1px;
background: var(--nb-und-ink);
transition: left 0.3s ease, right 0.3s ease;
}
.nb-und .alpha-links a:hover {
color: var(--nb-und-ink);
}
.nb-und .alpha-links a:hover::after {
left: 8px;
right: 8px;
}
.nb-und .showcase {
padding: 5rem 3rem;
border-top: 1px solid var(--nb-und-rule);
max-width: 1440px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 0;
}
.nb-und .showcase-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 2rem 0;
border-bottom: 1px solid var(--nb-und-rule);
cursor: pointer;
overflow: hidden;
position: relative;
}
.nb-und .showcase-title {
font-family: 'Libre Baskerville', serif;
font-size: clamp(2rem, 4vw, 4rem);
font-weight: 400;
color: var(--nb-und-ink);
line-height: 1;
position: relative;
display: inline-block;
transition: color 0.2s;
}
.nb-und .showcase-title::after {
content: '';
position: absolute;
bottom: 4px;
left: 0;
right: 100%;
height: 2px;
background: var(--nb-und-red);
transition: right 0.5s cubic-bezier(0.25, 1, 0.5, 1);
}
.nb-und .showcase-item:hover .showcase-title::after {
right: 0;
}
.nb-und .showcase-item:hover .showcase-title {
color: var(--nb-und-red);
}
.nb-und .showcase-right {
display: flex;
align-items: center;
gap: 1rem;
}
.nb-und .showcase-meta {
font-size: 0.72rem;
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--nb-und-ink-3);
}
.nb-und .showcase-arrow {
font-size: 1.5rem;
color: var(--nb-und-ink-3);
transform: translateX(-8px);
transition: transform 0.3s ease, color 0.2s;
}
.nb-und .showcase-item:hover .showcase-arrow {
transform: translateX(0);
color: var(--nb-und-red);
}
@media (max-width: 900px) {
.nb-und .hero {
grid-template-columns: 1fr;
gap: 2rem;
padding: 3rem 1.5rem 2rem;
}
.nb-und .nav-end a,
.nb-und .nav-end-dot {
display: none;
}
.nb-und .section {
padding: 3rem 1.5rem;
}
.nb-und .alphabet-demo {
margin: 2rem 1.5rem;
padding: 2rem 1rem;
}
.nb-und .showcase {
padding: 3rem 1.5rem;
}
.nb-und .demo-nav {
padding: 0 1rem;
}
.nb-und nav .nav-inner {
padding: 0 1.5rem;
}
}
@media (prefers-reduced-motion: reduce) {
.nb-und * {
transition: none !important;
}
}
```
## JavaScript
```js
(() => {
// Scoped magnetic-line wiring. Two instances of the magnetic line
// pattern run on the page (the main top nav + the demo nav in
// variant 01). Each is independently bound to its wrapper + nav
// + line + static-line via data-* attributes — no document.getElementById,
// no shared state, multiple instances coexist safely.
const root = document.querySelector('.nb-und');
if (!root) return;
function setupMagneticLine(navEl, wrapper, line, staticLine) {
if (!navEl || !wrapper || !line || !staticLine) return;
const links = navEl.querySelectorAll('a');
function positionLine(el, targetLine, useAccent) {
const wRect = wrapper.getBoundingClientRect();
const rect = el.getBoundingClientRect();
targetLine.style.left = (rect.left - wRect.left) + 'px';
targetLine.style.width = rect.width + 'px';
if (useAccent) targetLine.style.background = 'var(--nb-und-red)';
}
const active = navEl.querySelector('a.active');
if (active) {
requestAnimationFrame(() => positionLine(active, staticLine, true));
}
links.forEach((a) => {
a.addEventListener('mouseenter', () => {
positionLine(a, line);
line.classList.add('visible');
});
a.addEventListener('click', (e) => {
e.preventDefault();
links.forEach((l) => l.classList.remove('active'));
a.classList.add('active');
positionLine(a, staticLine, true);
});
});
wrapper.addEventListener('mouseleave', () => {
line.classList.remove('visible');
});
}
// Main top nav
setupMagneticLine(
root.querySelector('[data-nb-und-nav]'),
root.querySelector('[data-nb-und-wrap]'),
root.querySelector('[data-nb-und-line]'),
root.querySelector('[data-nb-und-static]'),
);
// Demo 1 (the in-page variant showcase)
setupMagneticLine(
root.querySelector('[data-nb-und-demo-nav]'),
root.querySelector('[data-nb-und-demo-wrap]'),
root.querySelector('[data-nb-und-demo-line]'),
root.querySelector('[data-nb-und-demo-static]'),
);
// Variant 2 / 3 click-to-activate (pure CSS hover; this just keeps the
// active state in sync after a click).
root.querySelectorAll('.nav-links-v2 a, .nav-links-v3 a').forEach((a) => {
a.addEventListener('click', (e) => {
e.preventDefault();
const siblings = a.closest('ul').querySelectorAll('a');
siblings.forEach((s) => s.classList.remove('active'));
a.classList.add('active');
});
});
// Alphabet ticker — 26 links built once.
const alpha = root.querySelector('[data-nb-und-alpha]');
if (alpha && alpha.childElementCount === 0) {
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').forEach((ch) => {
const a = document.createElement('a');
a.href = '#';
a.textContent = ch;
a.addEventListener('click', (e) => e.preventDefault());
alpha.appendChild(a);
});
}
})();
```