Custom Bezier Transition
Two synchronized CSS animations — link color and a left-to-right underline — both driven by the same cubic-bezier for a coherent hover.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Navigation has 36 effects, including 4 free. Explore the category →
3 usage examples



How it works
The effect relies on an ::after pseudo-element absolutely positioned at the bottom of the link. At rest, it is collapsed to zero with transform: scaleX(0) and anchored to the right (transform-origin: right center). On hover, it expands to scaleX(1) and its origin switches to left center — this anchor swap creates the left-to-right slide on enter, then right-to-left on exit, entirely in CSS.
The link color transitions simultaneously from --text-secondary (#a1a1aa, zinc-400) to #6366f1 (indigo-500). Both transitions share the same duration (0.4 s) and the same easing function cubic-bezier(.44, 0, .56, 1): this curve is more symmetric than a standard ease-in-out, with a faster peak at the midpoint and a gentle deceleration at both ends — giving the impression that the underline is searching for its final position.
No JavaScript is involved — the effect is purely declarative. The .link-color-bezier class can be applied to any inline text element (<a>, <span>, <button>) without touching the DOM or initializing a script. Link spacing is controlled by the CSS variable --space-8 (2 rem by default).
Accessibility
- prefers-reduced-motion: absent from the code. CSS transitions always run, even when the OS requests reduced motion. To fix this, add
@media (prefers-reduced-motion: reduce) { .link-color-bezier, .link-color-bezier::after { transition: none; } }. - Resting contrast:
#a1a1aaon dark background (#0a0a0f) ≈ 8.5:1 — exceeds WCAG AAA. - Hover contrast:
#6366f1on#0a0a0f≈ 4.7:1 — meets WCAG AA for normal text. - The hover state combines two simultaneous visual cues (color change + underline): colorblind users are not solely reliant on hue to perceive the active state.
- The demo
<span>elements are not keyboard-focusable by default. In production, use<a>or addtabindex="0"androle="link"for correct keyboard navigation.
Browser compatibility
Requires CSS transitions and CSS custom properties — supported in all modern browsers since 2017. Zero external dependencies.
Without CSS transition support, links display without animation — the color change and underline appearance remain functional but instantaneous.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<nav class="nav-demo">
<span class="link-color-bezier">Home</span>
<span class="link-color-bezier">Services</span>
<span class="link-color-bezier">Projects</span>
<span class="link-color-bezier">Contact</span>
</nav>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| --text-secondary | #a1a1aa | Resting link color (zinc-400). Replace with your palette's secondary color — #94a3b8 for slate, #d1d5db on light backgrounds. |
| --space-8 | 2rem | Horizontal gap between links in the .nav-demo container. Increase to 2.5–3 rem for an open nav, reduce to 1 rem for a compact one. |
| Accent color (#6366f1) | #6366f1 | Value duplicated in .link-color-bezier:hover { color } and .link-color-bezier::after { background } — update both identically to your brand color. |
| Duration (0.4s) | 0.4s | Duration in both transition properties (color and transform). 0.25s = responsive/tech, 0.6s = smooth/premium. Update the identical value in both .link-color-bezier and .link-color-bezier::after. |
| Easing cubic-bezier(.44, 0, .56, 1) | cubic-bezier(.44, 0, .56, 1) | Shared easing function for both color and underline. Replace with ease-out for a sharper feel, linear for a mechanical look, or cubic-bezier(.65, 0, .35, 1) for more pronounced acceleration. |
| Underline height (2px) | 2px | The height property of .link-color-bezier::after. 1px = thin editorial line, 3-4px = stronger brand accent. |
| Slide direction (transform-origin) | right center / left center | Swap both values — set left center at rest and right center on hover — for a right-to-left slide (suitable for RTL interfaces). |
FAQ
.link-color-bezier class directly to your <a> element and remove the browser's native text-decoration to avoid a double underline. The effect works on any inline element with position: relative.ease-in-out (cubic-bezier(.42, 0, .58, 1)) starts slightly earlier and ends later — the .44, 0, .56, 1 curve is more symmetric and produces a more pronounced speed peak at the midpoint, giving the impression that the underline snaps into place with more character. The difference is subtle but noticeable on short text.link-color-bezier class to your link components. No lifecycle hooks, no useEffect, no event listeners to clean up.