Flip Link — Free HTML CSS JS Per-Letter Hover Snippet

Flip Link · Navigation · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Per-letter split
JS wraps each character in its own span.
Pseudo-element flip
::after renders the incoming letter, no extra nodes.
Staggered cascade
--i and transition-delay roll letters in sequence.
Reversing exit
Leave flips the stagger direction for follow-through.
Clipped reveal
overflow hidden shows only one copy at a time.
GPU-friendly
translateY on inline-block stays smooth at scale.
Space-aware
Explicit width keeps multi-word gaps.
Clean markup
One word per link; the split is scripted.

About this UI Snippet

Flip Link — Per-Letter Vertical Flip Navigation

Screenshot of the Flip Link snippet rendered live

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

  1. 1
    Paste HTML, CSS, and JSA stack of large navigation links renders.
  2. 2
    Hover a linkIts letters flip vertically one after another, left to right.
  3. 3
    See the accent copyEach incoming letter arrives in an accent color.
  4. 4
    Leave the linkThe flip reverses from the end for follow-through.
  5. 5
    Tune the cascadeChange the per-letter delay step.
  6. 6
    Recolor the flipEdit the ::after accent color and easing.

Real-world uses

Common Use Cases

Editorial navigation
Big menus beside a fullscreen menu.
Agency sites
Pair with an agency hero.
Portfolio menus
Top a portfolio hero with kinetic links.
Footer link lists
Liven up a mega footer column.
Landing nav
Combine with a floating pill nav.
Kinetic type demos
A reference for per-letter staggered flips.

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.