
CSS Animated Underline on Hover
Published
30 hand-coded CSS text hover effect demos: 4-variant animated underlines, text fill + liquid fill, text-color reveal, strike-through, text swap / flip up, glitch RGB-split, neon flicker, gold shimmer, 3D pop, letter-wave stagger, cursor-tracked spotlight mask, spoiler reveal, blur-to-focus, magnetic pull, letter-spacing expansion, stroke-fill, scramble / matrix, border trace, rubber-band bounce, variable font-weight shift, oklch rainbow flow, perspective tilt, smoke dissolve, fire glow, cursor-vector text-shadow, marquee speed-up, riso duotone, tooltip reveal, SVG ripple, mask clip-path wipe. 25 Pure CSS + 5 tiny JS. Every effect fires on :hover AND :focus-visible so keyboard users get parity (WCAG 2.1.1). Scoped under .cth-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.
Related30 CSS Text Animations20 CSS Text Gradient Effects26 CSS Link Effects

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published
Published
Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

<button>s with aria-describedby pointing at screen-reader text, because a tooltip a screen reader can't hear is a decoration, not a tooltip.Published

<animate> keeps the wave rolling with zero JavaScript. The integration trick is the crossfade: filter:url() can't transition, so the headline keeps a crisp copy and a permanently-rippling aria-hidden clone stacked on it, and hover simply fades between them — the water rises smoothly over the word instead of snapping on.Published

Published
<a> tags in body text and navigation. Overlap with #01 underline in this collection is intentional — link-effects covers the 26-variant underline / hover treatment cluster for anchor-tag-specific use; text-hover-effects covers headings, wordmarks, and non-anchor text hovers. css-glitch-text-effect + css-neon-text-effect — deep-dive single-technique showcases (10-20 variants of glitch, 10-20 of neon). This collection ships ONE canonical hover-triggered glitch (Demo 06) + ONE canonical hover-triggered neon (Demo 07) as part of the broader hover toolkit. Use the deep-dive collections when you're picking THE glitch or THE neon; use this one when you're picking from a mixed toolkit of hover effects. The SEO angle: each collection ranks for its query cluster without cannibalizing siblings because the query intent differs — 'css text hover effect' searchers want a toolkit, 'css typewriter effect' searchers want the typewriter, 'css neon text' searchers want the neon library.:focus-visible. If the effect only fires on hover, keyboard users see NOTHING happen — they've focused an element and there's no visual feedback that they're on it. This fails WCAG 2.1.1 (Keyboard) and 2.4.7 (Focus Visible) — both AA-level violations that ADA lawsuits, Section 508 audits, and EU Accessibility Act enforcement (June 2025) actively cite. The 2-line fix: instead of .link:hover { ... }, write .link:hover, .link:focus-visible { ... }. Every one of the 30 demos in this collection ships this pattern. Verify by tabbing through — each effect should fire when the element gains keyboard focus, identically to hover. Additional non-negotiables: (1) prefers-reduced-motion guard per WCAG 2.3.3 — vestibular-sensitive users (~35% of adults per WebAIM 2024 survey) need continuous animations to CALM to a simple color/underline change under @media (prefers-reduced-motion: reduce). Every demo here ships this. (2) Never make color the SOLE indicator per WCAG 1.4.1 (Use of Color) — a gradient text hover that shifts hue but doesn't change weight/underline/decoration fails colorblind users. Pair gradient shifts with underline appearance, weight bump, or scale change. (3) Text-shadow blur on hover is a Core Web Vitals INP hazard on lower-end Android — animate opacity and filter on a pseudo-element instead of animating text-shadow blur-radius directly (compositor thread vs main thread). Demo 07 (neon) and Demo 24 (fire) both ship the compositor-thread pattern. (4) Focus rings must stay VISIBLE when the effect fires — a common mistake is having the hover transform mask the focus outline. Test with document.querySelector('.your-el').focus() and confirm the ring is visible..text-base in a low-contrast color (says 'INSIGHTS 2026'), and an overlay .text-photo that uses background-image: url(...) + background-clip: text; -webkit-background-clip: text; color: transparent. Result: the photo shows only within the letter shapes of the overlay. (2) Radial mask on the overlay: .text-photo { mask: radial-gradient(120px circle at var(--x) var(--y), black 0%, transparent 100%); -webkit-mask: same }. The radial gradient acts as a stencil — only the area within the circle is visible; outside is masked. (3) JS updates --x / --y from pointer position: 12-line vanilla IIFE with a pointermove listener on the container that writes e.offsetX + 'px' and e.offsetY + 'px' to CSS custom properties on the container. rAF-throttled so it hits 60fps on the compositor thread. Zero React state, zero re-renders. Result: as the user moves their cursor over the headline, a spotlight follows revealing the photo underneath. Falls back gracefully — if JS fails, the mask center stays at 50% 50% and the photo shows through a static spotlight. Same pattern Stripe uses on their homepage hero, Linear's about page, Vercel's docs landing, Cursor's Cmd+K teaser, v0's playground marketing. WCAG 2.1.1 fix: on :focus-visible, force the mask to full-visible (spotlight covers the entire text) so keyboard users see the photo reveal too. Demo 11 in this collection ships all three layers + the a11y fix. Bundle cost: 12 lines JS, ~500 bytes gzipped. Compare to Framer Motion (35KB+ + React) or GSAP ($99+/yr Business license for commercial use) — this pattern is native web platform at 0KB runtime.font-variation-settings API (Baseline: Chrome 62 / Safari 11 / Firefox 62 — universally supported since 2018) to smoothly morph text weight on hover instead of the old step-only font-weight: 400 → 700 jump. Recipe: (1) Load a variable font with the 'wght' axis exposed — Manrope, Inter, Recursive, Roboto Flex, Fraunces all ship this. Demo 20 uses Manrope. (2) Set the base state: .text { font-variation-settings: 'wght' 400 }. (3) On hover: .text:hover, .text:focus-visible { font-variation-settings: 'wght' 700 }. (4) Add the transition on the settings property: transition: font-variation-settings 0.3s cubic-bezier(0.4, 0, 0.2, 1). Result: the letterforms smoothly morph from 400 to 700 over 300ms — every intermediate weight (450, 500, 550, 600...) renders correctly because variable fonts interpolate the design space. The trap most tutorials miss: naïve implementations shift the layout as text gets bolder (bolder = wider). Fix: reserve the bold width upfront using a phantom span. Set .text { position: relative } and pseudo-element .text::before { content: attr(data-text); font-variation-settings: 'wght' 700; visibility: hidden; position: absolute; inset: 0 }. The pseudo reserves the widest state; the visible text morphs within that reserved box, so no layout shift (CLS 0). This is the pattern Linear docs, Vercel dashboard, and Stripe marketing all ship. Compare to the old approach: font-weight step animations require you to ship multiple font files (Regular 400 + Bold 700), the browser can't interpolate weights between them, and hover animations snap between the two — a hard jump instead of a smooth morph. Variable fonts ship ONE file with a continuous weight axis (typically 100-900) and let CSS animate through the axis smoothly. Font file size is comparable (Manrope variable = 60KB, Manrope Regular + Bold = 55KB, so effectively the same weight).-webkit- prefix (Chrome 3 / Safari 4 / Firefox 49). Every demo that uses it also declares -webkit-background-clip: text for Safari safety. Wrap in @supports ((background-clip: text) or (-webkit-background-clip: text)) { .el { color: transparent } } so the fallback (normal color: var(--ink)) is used when unsupported — this is the invisible-text WCAG 1.4.3 fix. Demo 27 in this collection ships this exact @supports guard. @property — Baseline widely available since April 2024 (Chrome 85 / Safari 16.4 / Firefox 128). Registers custom properties with typed syntax so they interpolate smoothly. Used in Demos 02 (fill %), 18 (border angle), 30 (mask position). Without it, the properties still work but hard-jump instead of interpolating. oklch() + color-mix() — Baseline widely available since 2023 (Chrome 111 / Safari 15.4 / Firefox 113). Falls back to browser default. text-shadow + filter — universal support since 2011. -webkit-text-stroke — Baseline widely available since 2018 (Chrome 4 / Safari 3.1 / Firefox 49). mask-image / mask — Baseline widely available since 2023 (Chrome 120 / Safari 15.4 / Firefox 53). Pair -webkit-mask + unprefixed mask. container queries — Baseline widely available since February 2023 (Chrome 105 / Safari 16 / Firefox 110). SVG feTurbulence + feDisplacementMap (Demo 29) — universal since 2010. font-variation-settings (Demo 20) — universal since 2018. attr(data-text) in content (Demos 06 + 27) — universal since 2011. Progressive enhancement recipe: layer declarations from oldest-supported to newest, so old browsers get a functional (if less polished) baseline. Every demo in this collection stays FUNCTIONAL in every browser since 2018 — you still get text with a hover color/underline change, just without the advanced effect. Enterprise procurement (Fortune 500 IT with corporate Windows fleets) tests this fallback path — this collection passes. Google Fonts @import is used ONCE in the collection (Demo 20 for Manrope variable font). Header comment authorizes it because the variable-font weight axis IS the demo's whole point; every other demo uses system font stacks.class to className, drop the JSX into any server or client component (no 'use client' directive needed for the 25 Pure CSS demos because they have no interactivity that requires hydration; only Demos 11 (spotlight cursor) + 14 (magnetic cursor) + 17 (scramble) + 25 (shadow vector) + 26 (marquee speed) need it, each under 25L). Vue 3 / Nuxt 3: accepts as-is, class attribute is native. Svelte / SvelteKit: accepts as-is. Astro: drop into any .astro component (this site is Astro; every demo renders SSR on this page). Remix / Solid.js / Qwik: accepts as-is. Tailwind CSS v3/v4: hover:underline/hover:decoration-* utilities map 1:1 for Demo 01; complex demos (glitch, spotlight, magnetic) require Tailwind's arbitrary-value syntax or @layer components block. Compared to text-animation JavaScript libraries: Framer Motion (~35KB gzipped + React dep + Motion Values state management) — provides text-animation primitives but each hover effect this collection ships in 20-60 lines of CSS costs 100-300 lines in Framer Motion + the runtime overhead. GSAP (SplitText plugin: $99+/yr Business license for commercial use since 2024 relicense to Business tier only — the free Standard tier no longer includes SplitText) — text-hover effects like glitch, scramble, magnetic are all doable in GSAP but require the ScrollTrigger + SplitText plugins which cost annually. This collection ships every one of those patterns MIT-licensed. split-type / splitting.js — ~15KB gzipped libraries that split text into per-letter spans for stagger animation. Demo 10 (Split Letter Wave) ships the same result with pre-split HTML markup + calc() delay, zero library. Aceternity UI / Magic UI — React + Tailwind text-hover component libraries ($299+/yr for Aceternity Pro / open-source Magic UI ~150KB deps). Every visual pattern they ship is rebuildable in native CSS from this collection's recipes. The .cth-NN numeric-prefix scoping means all 30 text hover patterns coexist on the gallery page without a single class collision, and the same prefix guarantees you can drop any one demo into a codebase that already uses .text, .headline, .link, or .wordmark classes without renaming a single selector. MIT licensed, no attribution required, no signup.20 hand-coded CSS animated buttons — neon glow, ripple, 3D press, liquid fill, jelly bounce, shine sweep, animated border, moving gradient CTA, text flip, submit success state, add-to-cart progress, download icon, hamburger-to-close, toggle switch, loading spinner inside button, next/prev arrow nav, and ghost button background reveal. Half pure CSS, half lightweight JS for production interactions.
11 pure CSS animation-timeline demos built on the W3C scroll() and view() spec — reading progress bar, view-timeline fade & reveal, shrinking sticky header, stacking cards, horizontal scroll section, parallax hero, container shadow indicators, reverse-scroll columns, text scrim reveal, cover card to sticky header, and image zoom / clip-path wipe. Zero JS, off the main thread, Core Web Vitals safe. Copy-paste ready.
25 hand-coded CSS background animations for SaaS hero sections, developer tool documentation, agency portfolios, gaming and Web3 landing pages, seasonal e-commerce campaigns, and dark-mode dashboards. Covers full-screen gradient shifts, animated mesh gradients, aurora borealis glow, cursor spotlight follow, interactive water ripples, dot grid and diagonal stripe patterns, SVG grain and noise texture overlays, topographic contour lines, layered SVG waves, plasma and lava lamp fluids, bokeh light blur, particle constellation networks, matrix digital rain, synthwave 3D perspective grids, starfield warp speed, CRT scanline overlays, fog and mist drift, morphing organic blobs, floating glassmorphism orbs, halftone dot patterns, starry night skies, floating geometric shapes, rising bubbles, and falling snow. 20 are pure CSS with zero JavaScript; the 5 that need it use vanilla canvas or pointer tracking with no dependencies. Every background animates compositor-only properties so it never blocks the main thread, is scoped under a .bga-NN prefix for no-collision pasting, guards prefers-reduced-motion, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.