Skip to content

underline

aa-hover="underline-in" and aa-hover="underline" add an animated underline bar at the bottom of the host. The library appends a single <span aa-hover-underline> and animates its scaleX — no HTML changes required. Rapid flicks stay directionally clean via an asymmetric interrupt model: a leave during IN is queued (the full IN runs first, then OUT), and an enter during OUT fast-forwards the in-flight shrink to ~100 ms before growing back in from the left — so OUT always retracts rightward and IN always grows leftward, even on quick reversals.

Touch-only devices skip the effect via (hover: hover) so iOS / Android don't fire stuck animations on tap.

VariantDefault stateOn hover
underline-inNo underlineBar grows left → right; on leave, shrinks right-ward back to 0.
underlineUnderline always shownBar collapses toward the right edge, then re-grows from the left. Two phases.
AttributeDefaultNotes
aa-hover(required)underline-in or underline
aa-colorcurrentColorBar background-color
aa-duration(init)Seconds. For underline, this is the total sweep time (split into two halves).
aa-delay0Seconds before the enter animation starts.
aa-ease(init)Any GSAP ease — falls back to init({ ease }) (default power4.out).

The injected bar's layout (position, width, height, transform-origin, the offset math) all live in alrdy-animate.css — that import is required for the underline head, see the Next.js install guide or the Webflow install guide. Without it the bar renders as a static unstyled span.

Set on the host, an ancestor, or :root. In Webflow, paste into a Custom Code embed <style> block — the Designer's Style panel can't author CSS custom properties.

VariableDefaultNotes
--aa-hover-underline-thickness0.0625emBar height.
--aa-hover-underline-offset0Positive pushes the bar further below the text; negative pulls it up. Auto-bumped to 0.05em for bars inside a <p> so inline links clear deep descenders.

The hover feature is skipped entirely on (hover: none) devices — touch screens have no reliable mouseenter, so no bar is injected. For the always-on underline variant that would leave the link looking like plain text, so the CSS file paints a static native text-decoration underline there instead (honouring --aa-hover-underline-thickness; the offset is left at the browser default so skip-ink steps cleanly around descenders). The link still reads as a link; only the sweep animation is dropped. underline-in gets no fallback — its rest state is intentionally line-less, so there's nothing to preserve.

gsap.

Each link's bar sweeps in, then back out. Flick across rapidly — the queue-up model keeps every cycle clean.

The line is permanent; hover collapses it to the right edge, then re-grows it from the left. Each phase runs for half of aa-duration.

Live demo — thick underline via CSS variable

Section titled “Live demo — thick underline via CSS variable”

--aa-hover-underline-thickness: 0.2em produces a heavy bar; a small positive --aa-hover-underline-offset pushes it clear of deep descenders.

Live demo — coloured underline (independent of text)

Section titled “Live demo — coloured underline (independent of text)”

aa-color overrides the bar's background-color so the underline reads in a brand colour while the text stays black.

The bar lives inside the host with position: absolute; bottom: 0; left: 0; width: 100% — so it lands at the bottom edge of the host's content box. Two recipes cover almost every case.

For nav links, CTAs, or anything where the <a> doesn't live inside flowing paragraph text. Symmetric vertical padding adds clearance below the descenders and widens the hoverable area above the cap-height, so the cursor doesn't have to land exactly on the glyphs.

.nav-link {
display: inline-block;
line-height: 1;
padding: 0.3em 0;
color: inherit;
text-decoration: none;
}
<a class="nav-link" href="/work" aa-hover="underline">Work</a>

Leave layout alone — the default inline <a>'s bounding box already matches the glyph extent, so the bar lands near the descender baseline. The lib auto-sets --aa-hover-underline-offset: 0.05em on every <p>, mirroring the gap browsers use for text-decoration: underline, so this works out of the box. For deep-descender fonts (g, j, y) bump the offset higher with --aa-hover-underline-offset: 0.1em on the <p> (or any ancestor).

For other block-level text containers (<li>, <blockquote>, custom <div> paragraphs), set the variable yourself to opt in:

.prose { --aa-hover-underline-offset: 0.05em; }
p a {
color: inherit;
text-decoration: none;
}
We rebuilt the studio site last year, and the writeup of the migration walks through how a small team shipped a multi-page Webflow rebuild without touching a CMS field manually. The follow-up notes cover what we'd do differently next time.