More Navigation Snippets
Flip Link — Free HTML CSS JS Per-Letter Hover Snippet
Flip Link · Navigation · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Flip Link — Per-Letter Vertical Flip Navigation

The flip link is the oversized navigation effect on editorial and agency sites: hover a menu item and each letter rolls vertically one after another, the original sliding up and out while a duplicate in an accent color rolls in from below. This snippet builds it with plain HTML, CSS, and a small vanilla JavaScript splitter, using only transforms and a pseudo-element — no duplicated markup per word.
Splitting text into per-letter spans
On load, JavaScript clears each link's text and rewraps every character in its own .fl-char span. Each span stores its index in a --i custom property (for the stagger) and its character in a data-c attribute. Wrapping letters individually is required because the flip animates each one separately — you can't transform parts of a single text node — and the per-letter index is what enables the cascading roll.
The flip with one pseudo-element
Each letter span shows its character normally, and its ::after pseudo-element renders the same character (via content: attr(data-c)) positioned directly below it at top: 100% in the accent color. The span has overflow: hidden on the link, so only the top copy is visible at rest. On hover the span translates up by -100%: the original slides out the top while the ::after duplicate slides into view from the bottom. Because both copies move together inside the clipped link, it reads as a single letter flipping over — and it needs no second copy of the word in the HTML.
The staggered cascade
The signature roll comes from transition-delay: calc(var(--i) * 22ms), so each letter starts its flip 22ms after the previous one. The result is a wave that sweeps across the word left to right rather than every letter flipping at once. This small delay-per-index is the whole trick behind the premium, kinetic feel.
Reversing on exit
To make the effect feel physical, the JavaScript flips the stagger direction on leave: pointerenter sets ascending delays (first letter first), while pointerleave sets descending delays (last letter first). So the word rolls in from the front and rolls back from the end, giving the animation a sense of follow-through instead of simply replaying backward in lockstep.
Spaces handled
Space characters get an explicit width via a [data-c=' '] rule so multi-word links keep their gaps, since an empty inline-block span would otherwise collapse.
Why transforms and a pseudo-element
Using translateY on inline-block spans keeps the animation GPU-friendly and smooth even at the large font sizes these links use. Rendering the incoming letter as a ::after rather than a second DOM node halves the element count and keeps the markup to one clean word per link — the split happens in script.
Customizing it
Change the 22ms step for a faster or slower cascade, swap the accent color of the ::after, adjust the cubic-bezier for a snappier or softer roll, or split on words instead of letters for a chunkier flip. Pair it with a fullscreen menu overlay or a floating pill nav for a complete navigation system.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA stack of large navigation links renders.
- 2Hover a linkIts letters flip vertically one after another, left to right.
- 3See the accent copyEach incoming letter arrives in an accent color.
- 4Leave the linkThe flip reverses from the end for follow-through.
- 5Tune the cascadeChange the per-letter delay step.
- 6Recolor the flipEdit the ::after accent color and easing.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Because each letter flips independently and on its own delay. You can't transform parts of a single text node, so JavaScript rewraps every character in a span that carries its index in a --i variable. That per-letter index is what drives the staggered, cascading roll across the word.
Each letter span has an ::after pseudo-element that renders the same character via content: attr(data-c), positioned just below it in the accent color. On hover the span translates up 100%, so the top copy slides out and the ::after copy slides in from below within the clipped link. The incoming letter is a pseudo-element, so no second word is needed in the HTML.
Each letter's transition-delay is calc(var(--i) * 22ms), so letter i starts 22 milliseconds after the one before it. That per-index delay turns a simultaneous flip into a wave that sweeps across the word, which is the effect's signature kinetic feel.
On pointerenter the script sets ascending delays so the first letter flips first; on pointerleave it sets descending delays so the last letter flips back first. Rolling in from the front and out from the end gives the animation follow-through, rather than just replaying the same order backward.
Render each link's characters as an array of spans with their index as a --i style, rather than splitting in a DOM effect. Set the transition-delay direction based on a hovered/leaving state. The flip CSS ports directly. In Tailwind, use translate utilities with arbitrary transition-delay values per index, and an after: pseudo with content from a data attribute.