Underline Expand
A 2 px underline grows symmetrically from the center of the link on hover — pure CSS, zero JavaScript.
This effect is free — the Navigation category contains 36 effects total, including 4 free. Effect.Labs has 811 vanilla effects. Explore the category →
Usage examples
Build faster,
ship with confidence
The platform that scales with your team. From prototype to production in minutes.
How it works
The effect relies on an ::after pseudo-element anchored to the link's center via left: 50% and transform: translateX(-50%). At rest, width: 0 hides it. On hover, width: 100% makes it grow toward both edges simultaneously — this centering is what creates the illusion of expansion from the middle rather than from one side.
The underline background uses var(--gradient-primary), inheriting the gradient defined in the host theme. The 0.3 s CSS transition (transition: 0.3s on ::after) animates the width smoothly, with zero JavaScript and no runtime measurements.
At the same time, the text color shifts from var(--text-secondary) to var(--text-primary) via its own transition: color 0.3s declaration. The two transitions target distinct properties and run without conflict — one animates width on ::after, the other animates color on the parent element.
The effect is fully declarative: no JS, no event listeners. Add the class .link-underline-expand to any <a>, <span>, or <button> with position: relative to activate it immediately.
Accessibility
- prefers-reduced-motion not present in the code: the 0.3 s transition runs even when the OS signals a preference for reduced motion. Recommended fix:
@media (prefers-reduced-motion: reduce) { .link-underline-expand::after { transition: none; } }. - The effect is purely decorative: the text color also changes on hover, providing a second state indicator independent of the underline — useful for color-blind users or those who disable pseudo-elements.
- The demo code uses
<span>elements — in production, use native<a>elements and add.link-underline-expand:focus-visible::after { width: 100%; }to activate the effect on keyboard navigation. - Contrast depends on the host theme's CSS variables. In the Effect.Labs dark theme,
--text-secondary(#a1a1aa on #0a0a0f) reaches a ~4.6:1 ratio, meeting WCAG AA. - The effect only adds visual information — no content is hidden or removed from the flow, no impact on screen readers.
Browser compatibility
Requires ::after, CSS transitions and custom properties (var()) — supported in all modern browsers since 2017.
Without CSS custom properties (<code>var()</code>), browser default colors apply. Without CSS transitions, the underline appears and disappears instantly — functional but without animation.
The code
Copy the three blocks into your page. No dependencies.
<div class="nav-demo">
<span class="nav-link-demo link-underline-expand">Accueil</span>
<span class="nav-link-demo link-underline-expand">Services</span>
<span class="nav-link-demo link-underline-expand">Contact</span>
</div>
.nav-demo {
display: flex;
gap: var(--space-8);
align-items: center;
}
.nav-link-demo {
color: var(--text-secondary);
font-weight: 500;
padding: var(--space-2) 0;
position: relative;
cursor: pointer;
transition: color 0.3s;
}
.nav-link-demo:hover {
color: var(--text-primary);
}
.link-underline-expand::after {
content: "";
position: absolute;
bottom: 0px;
left: 50%;
width: 0px;
height: 2px;
background: var(--gradient-primary);
transition: 0.3s;
transform: translateX(-50%);
}
.link-underline-expand:hover::after {
width: 100%;
}
// Effet CSS pur — pas de JavaScript nécessaire
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| var(--gradient-primary) | indigo→violet→pink gradient | Underline color or gradient. Replace with a solid color (#6366f1) or a custom gradient. |
| height: 2px (in ::after) | 2px | Line thickness. 1 px = ultra-subtle, 3–4 px = prominent. |
| bottom: 0px (in ::after) | 0px | Vertical offset from the baseline. A negative value like -4px moves the line away from the text. |
| transition: 0.3s (in ::after) | 0.3s | Expansion animation duration. 0.15 s = snappy, 0.5 s = cinematic. |
| width: 100% (in :hover::after) | 100% | Maximum underline width on hover. 80% gives a slightly shorter line, centered below the text. |
| var(--text-secondary) / var(--text-primary) | #a1a1aa / #ffffff | Text colors at rest and on hover, defined in the host theme. |
| var(--space-8) (gap in .nav-demo) | 2rem | Spacing between navigation links. Reduce to 1rem for a more compact navigation. |
FAQ
Can the underline stop at 80% width rather than spanning the full link?
Yes — change .link-underline-expand:hover::after { width: 80%; }. The line stays centered via left: 50% + translateX(-50%), so it will always be centered below the text regardless of the target width.
How do I trigger the effect on keyboard focus, not just mouse hover?
Add .link-underline-expand:focus-visible::after { width: 100%; } to your CSS. This activates the underline during keyboard navigation (Tab) with the same transition, without affecting mouse behavior.
Can I combine a static underline at rest with the expand effect on hover?
Yes — set a non-zero width at rest, for example .link-underline-expand::after { width: 20%; }. On hover it will grow to 100% with the transition, creating a growth effect rather than an appearance from zero.