14 / 30
All 30 in 30 Tailwind CSS Text Hover Effects
- 01Tailwind CSS Animated Underline Hover Effect
- 02Tailwind Liquid Text Fill Hover Animation
- 03Tailwind Gradient Text Hover Effect with bg-clip-text
- 04Tailwind Animated Strikethrough Text Hover Effect
- 05Tailwind Text Swap and Flip Up Hover Effect
- 06Tailwind CSS Glitch Text Hover Effect
- 07Tailwind Neon Glow Text with Flicker Animation
- 08Tailwind Metallic Gold Shimmer Text Effect
- 09Tailwind 3D Text Pop and Lift Hover Effect
- 10Tailwind Staggered Letter Wave Hover Animation
- 11Tailwind Spotlight Text Reveal on Mouse Hover
- 12Tailwind Hover to Reveal Hidden Spoiler Text
- 13Tailwind Blur to Focus Text Hover Effect
- 14Tailwind Magnetic Hover Effect for Buttons and Text
- 15Tailwind Letter Spacing Expand on Hover (Tracking Animation)
- 16Tailwind Outline Text Stroke to Fill Hover Effect
- 17Tailwind Text Scramble and Matrix Decrypt Hover Effect
- 18Tailwind Animated Border Button Hover Effect
- 19Tailwind Elastic Rubber Band Text Hover Animation
- 20Tailwind Variable Font Weight Hover Animation
- 21Tailwind Animated Rainbow Gradient Text on Hover
- 22Tailwind 3D Perspective Tilt Text Hover Effect
- 23Tailwind Smoke Dissolve Text Hover Effect
- 24Tailwind Fire Text Effect with Heat Wave Glow
- 25Tailwind Dynamic Text Shadow that Follows the Cursor
- 26Tailwind Infinite Text Marquee that Speeds Up on Hover
- 27Tailwind Duotone Split Color Text Hover Effect
- 28Tailwind CSS Tooltip on Hover (No JavaScript)
- 29Tailwind SVG Water Ripple Distortion Text Effect
- 30Tailwind Clip-Path Text Reveal Hover Animation
Tailwind Magnetic Hover Effect for Buttons and Text
Awwwards-style magnetic pills and a giant email link that lean toward the cursor. A tiny script writes --tth14-mx/--tth14-my; the markup reads them with translate-x-[var(--tth14-mx,0px)], and the label leans 55% further for parallax. Release snaps back on an elastic not-hover:ease-[cubic-bezier(.22,1.6,.36,1)].
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tth-14 w-full min-h-svh grid place-items-center bg-tth14-bg bg-[radial-gradient(80%_60%_at_50%_40%,var(--color-tth14-glow),var(--color-tth14-bg)_75%)] px-5 py-14 font-tth14-sans text-tth14-ink" aria-label="Magnetic cursor tracking text demo">
<div class="flex flex-col items-center gap-[clamp(26px,5vw,44px)] text-center">
<p class="text-xs font-bold uppercase tracking-[0.26em] text-tth14-mut">Bureau Nocturne — digital practice, Toronto</p>
<div class="flex flex-wrap justify-center gap-[clamp(14px,3vw,26px)]">
<a id="tth14-top" href="#tth14-top" data-tth14-magnet data-pull="0.42" class="grid min-h-14 place-items-center rounded-full bg-tth14-acc px-[30px] text-base font-extrabold tracking-[0.01em] text-tth14-on-acc no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,box-shadow] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:shadow-[0_14px_40px_-10px_var(--color-tth14-acc)] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">View work</span></a>
<a href="#tth14-top" data-tth14-magnet data-pull="0.42" class="grid min-h-14 place-items-center rounded-full bg-tth14-acc px-[30px] text-base font-extrabold tracking-[0.01em] text-tth14-on-acc no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,box-shadow] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:shadow-[0_14px_40px_-10px_var(--color-tth14-acc)] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">Process</span></a>
<a href="#tth14-top" data-tth14-magnet data-pull="0.42" class="grid min-h-14 place-items-center rounded-full border-[1.5px] border-tth14-ink/35 px-[30px] text-base font-extrabold tracking-[0.01em] no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,border-color,background-color] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:border-tth14-ink hover:bg-tth14-ink/6 focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">About</span></a>
</div>
<a href="#tth14-top" data-tth14-magnet data-pull="0.2" class="group/mail inline-flex items-baseline gap-[0.35em] border-b-2 border-tth14-ink/25 pb-[0.12em] font-tth14-display text-[clamp(26px,5.6vw,52px)] font-semibold tracking-[-0.02em] no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,border-color] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:border-tth14-acc focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">hello@nocturne.studio</span><span aria-hidden="true" class="inline-block text-tth14-acc translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit] group-hover/mail:rotate-45">↗</span></a>
<p class="max-w-[54ch] text-[12.5px] leading-[1.7] text-tth14-mut">Circle the cursor around a pill — it leans in, the label leans further, release snaps back on an elastic bezier. JS measures; Tailwind moves.</p>
</div>
</section><section class="tth-14 w-full min-h-svh grid place-items-center bg-tth14-bg bg-[radial-gradient(80%_60%_at_50%_40%,var(--color-tth14-glow),var(--color-tth14-bg)_75%)] px-5 py-14 font-tth14-sans text-tth14-ink" aria-label="Magnetic cursor tracking text demo">
<div class="flex flex-col items-center gap-[clamp(26px,5vw,44px)] text-center">
<p class="text-xs font-bold uppercase tracking-[0.26em] text-tth14-mut">Bureau Nocturne — digital practice, Toronto</p>
<div class="flex flex-wrap justify-center gap-[clamp(14px,3vw,26px)]">
<a id="tth14-top" href="#tth14-top" data-tth14-magnet data-pull="0.42" class="grid min-h-14 place-items-center rounded-full bg-tth14-acc px-[30px] text-base font-extrabold tracking-[0.01em] text-tth14-on-acc no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,box-shadow] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:shadow-[0_14px_40px_-10px_var(--color-tth14-acc)] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">View work</span></a>
<a href="#tth14-top" data-tth14-magnet data-pull="0.42" class="grid min-h-14 place-items-center rounded-full bg-tth14-acc px-[30px] text-base font-extrabold tracking-[0.01em] text-tth14-on-acc no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,box-shadow] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:shadow-[0_14px_40px_-10px_var(--color-tth14-acc)] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">Process</span></a>
<a href="#tth14-top" data-tth14-magnet data-pull="0.42" class="grid min-h-14 place-items-center rounded-full border-[1.5px] border-tth14-ink/35 px-[30px] text-base font-extrabold tracking-[0.01em] no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,border-color,background-color] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:border-tth14-ink hover:bg-tth14-ink/6 focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">About</span></a>
</div>
<a href="#tth14-top" data-tth14-magnet data-pull="0.2" class="group/mail inline-flex items-baseline gap-[0.35em] border-b-2 border-tth14-ink/25 pb-[0.12em] font-tth14-display text-[clamp(26px,5.6vw,52px)] font-semibold tracking-[-0.02em] no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,border-color] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:border-tth14-acc focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">hello@nocturne.studio</span><span aria-hidden="true" class="inline-block text-tth14-acc translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit] group-hover/mail:rotate-45">↗</span></a>
<p class="max-w-[54ch] text-[12.5px] leading-[1.7] text-tth14-mut">Circle the cursor around a pill — it leans in, the label leans further, release snaps back on an elastic bezier. JS measures; Tailwind moves.</p>
</div>
</section>@import "tailwindcss";
@theme {
--color-tth14-bg: #100e15;
--color-tth14-bg: oklch(0.16 0.01 290);
--color-tth14-glow: #1a1721;
--color-tth14-glow: oklch(0.21 0.017 295);
--color-tth14-ink: #f1eff5;
--color-tth14-ink: oklch(0.95 0.005 290);
--color-tth14-mut: #a29eaa;
--color-tth14-mut: oklch(0.72 0.015 290);
--color-tth14-acc: #f7a93b;
--color-tth14-acc: oklch(0.8 0.15 70);
--color-tth14-on-acc: #2c1b06;
--color-tth14-on-acc: oklch(0.22 0.04 70);
--font-tth14-sans: 'Onest', system-ui, sans-serif;
--font-tth14-display: 'Familjen Grotesk', system-ui, sans-serif;
}
.tth-14 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tth14-bg: #100e15;
--color-tth14-bg: oklch(0.16 0.01 290);
--color-tth14-glow: #1a1721;
--color-tth14-glow: oklch(0.21 0.017 295);
--color-tth14-ink: #f1eff5;
--color-tth14-ink: oklch(0.95 0.005 290);
--color-tth14-mut: #a29eaa;
--color-tth14-mut: oklch(0.72 0.015 290);
--color-tth14-acc: #f7a93b;
--color-tth14-acc: oklch(0.8 0.15 70);
--color-tth14-on-acc: #2c1b06;
--color-tth14-on-acc: oklch(0.22 0.04 70);
--font-tth14-sans: 'Onest', system-ui, sans-serif;
--font-tth14-display: 'Familjen Grotesk', system-ui, sans-serif;
}
.tth-14 {
width: 100%;
min-height: 100svh;
}(function () {
var magnets = document.querySelectorAll('[data-tth14-magnet]');
if (!magnets.length) return;
var reduced = matchMedia('(prefers-reduced-motion: reduce)');
var fine = matchMedia('(pointer: fine)');
magnets.forEach(function (el) {
var pull = parseFloat(el.getAttribute('data-pull')) || 0.35;
el.addEventListener('pointermove', function (e) {
if (reduced.matches || !fine.matches) return;
var r = el.getBoundingClientRect();
var x = (e.clientX - r.left - r.width / 2) * pull;
var y = (e.clientY - r.top - r.height / 2) * pull;
el.style.setProperty('--tth14-mx', x.toFixed(1) + 'px');
el.style.setProperty('--tth14-my', y.toFixed(1) + 'px');
});
el.addEventListener('pointerleave', function () {
el.style.setProperty('--tth14-mx', '0px');
el.style.setProperty('--tth14-my', '0px');
});
});
})();(function () {
var magnets = document.querySelectorAll('[data-tth14-magnet]');
if (!magnets.length) return;
var reduced = matchMedia('(prefers-reduced-motion: reduce)');
var fine = matchMedia('(pointer: fine)');
magnets.forEach(function (el) {
var pull = parseFloat(el.getAttribute('data-pull')) || 0.35;
el.addEventListener('pointermove', function (e) {
if (reduced.matches || !fine.matches) return;
var r = el.getBoundingClientRect();
var x = (e.clientX - r.left - r.width / 2) * pull;
var y = (e.clientY - r.top - r.height / 2) * pull;
el.style.setProperty('--tth14-mx', x.toFixed(1) + 'px');
el.style.setProperty('--tth14-my', y.toFixed(1) + 'px');
});
el.addEventListener('pointerleave', function () {
el.style.setProperty('--tth14-mx', '0px');
el.style.setProperty('--tth14-my', '0px');
});
});
})();Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tth14-bg: #100e15;
--color-tth14-bg: oklch(0.16 0.01 290);
--color-tth14-glow: #1a1721;
--color-tth14-glow: oklch(0.21 0.017 295);
--color-tth14-ink: #f1eff5;
--color-tth14-ink: oklch(0.95 0.005 290);
--color-tth14-mut: #a29eaa;
--color-tth14-mut: oklch(0.72 0.015 290);
--color-tth14-acc: #f7a93b;
--color-tth14-acc: oklch(0.8 0.15 70);
--color-tth14-on-acc: #2c1b06;
--color-tth14-on-acc: oklch(0.22 0.04 70);
--font-tth14-sans: 'Onest', system-ui, sans-serif;
--font-tth14-display: 'Familjen Grotesk', system-ui, sans-serif;
}
.tth-14 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tth14-bg: #100e15;
--color-tth14-bg: oklch(0.16 0.01 290);
--color-tth14-glow: #1a1721;
--color-tth14-glow: oklch(0.21 0.017 295);
--color-tth14-ink: #f1eff5;
--color-tth14-ink: oklch(0.95 0.005 290);
--color-tth14-mut: #a29eaa;
--color-tth14-mut: oklch(0.72 0.015 290);
--color-tth14-acc: #f7a93b;
--color-tth14-acc: oklch(0.8 0.15 70);
--color-tth14-on-acc: #2c1b06;
--color-tth14-on-acc: oklch(0.22 0.04 70);
--font-tth14-sans: 'Onest', system-ui, sans-serif;
--font-tth14-display: 'Familjen Grotesk', system-ui, sans-serif;
}
.tth-14 {
width: 100%;
min-height: 100svh;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Onest:wght@400..900&family=Familjen+Grotesk:wght@400..700&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Onest:wght@400..900&family=Familjen+Grotesk:wght@400..700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
.magnet:not(:hover){transition:… cubic-bezier(.22,1.6,.36,1)}becamenot-hover:duration-600 not-hover:ease-[…];translate:var(--mx) var(--my)becametranslate-x-[var(--tth14-mx,0px)]+translate-y-[…].- The script selects
[data-tth14-magnet]and writes namespaced--tth14-*properties — no class names in JS. - UI upgrade from the CSS original: the script now also checks
(pointer: fine), so a tap on a phone no longer leaves a pill stuck off-centre; pills gain an accent glow on hover and the ↗ arrow rotates. - UI upgrade: Onest + Familjen Grotesk replace the system stack, and muted copy rose from
0.6to0.72L (7:1).
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Magnetic Hover Effect for Buttons and Text
Source: https://codefronts.com/motion/tailwind-css-text-hover-effects/tailwind-magnetic-hover-effect-for-buttons-and-text/
Awwwards-style magnetic pills and a giant email link that lean toward the cursor. A tiny script writes --tth14-mx/--tth14-my; the markup reads them with translate-x-[var(--tth14-mx,0px)], and the label leans 55% further for parallax. Release snaps back on an elastic not-hover:ease-[cubic-bezier(.22,1.6,.36,1)].
## HTML
```html
<section class="tth-14 w-full min-h-svh grid place-items-center bg-tth14-bg bg-[radial-gradient(80%_60%_at_50%_40%,var(--color-tth14-glow),var(--color-tth14-bg)_75%)] px-5 py-14 font-tth14-sans text-tth14-ink" aria-label="Magnetic cursor tracking text demo">
<div class="flex flex-col items-center gap-[clamp(26px,5vw,44px)] text-center">
<p class="text-xs font-bold uppercase tracking-[0.26em] text-tth14-mut">Bureau Nocturne — digital practice, Toronto</p>
<div class="flex flex-wrap justify-center gap-[clamp(14px,3vw,26px)]">
<a id="tth14-top" href="#tth14-top" data-tth14-magnet data-pull="0.42" class="grid min-h-14 place-items-center rounded-full bg-tth14-acc px-[30px] text-base font-extrabold tracking-[0.01em] text-tth14-on-acc no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,box-shadow] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:shadow-[0_14px_40px_-10px_var(--color-tth14-acc)] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">View work</span></a>
<a href="#tth14-top" data-tth14-magnet data-pull="0.42" class="grid min-h-14 place-items-center rounded-full bg-tth14-acc px-[30px] text-base font-extrabold tracking-[0.01em] text-tth14-on-acc no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,box-shadow] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:shadow-[0_14px_40px_-10px_var(--color-tth14-acc)] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">Process</span></a>
<a href="#tth14-top" data-tth14-magnet data-pull="0.42" class="grid min-h-14 place-items-center rounded-full border-[1.5px] border-tth14-ink/35 px-[30px] text-base font-extrabold tracking-[0.01em] no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,border-color,background-color] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:border-tth14-ink hover:bg-tth14-ink/6 focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">About</span></a>
</div>
<a href="#tth14-top" data-tth14-magnet data-pull="0.2" class="group/mail inline-flex items-baseline gap-[0.35em] border-b-2 border-tth14-ink/25 pb-[0.12em] font-tth14-display text-[clamp(26px,5.6vw,52px)] font-semibold tracking-[-0.02em] no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,border-color] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:border-tth14-acc focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">hello@nocturne.studio</span><span aria-hidden="true" class="inline-block text-tth14-acc translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit] group-hover/mail:rotate-45">↗</span></a>
<p class="max-w-[54ch] text-[12.5px] leading-[1.7] text-tth14-mut">Circle the cursor around a pill — it leans in, the label leans further, release snaps back on an elastic bezier. JS measures; Tailwind moves.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tth14-bg: #100e15;
--color-tth14-bg: oklch(0.16 0.01 290);
--color-tth14-glow: #1a1721;
--color-tth14-glow: oklch(0.21 0.017 295);
--color-tth14-ink: #f1eff5;
--color-tth14-ink: oklch(0.95 0.005 290);
--color-tth14-mut: #a29eaa;
--color-tth14-mut: oklch(0.72 0.015 290);
--color-tth14-acc: #f7a93b;
--color-tth14-acc: oklch(0.8 0.15 70);
--color-tth14-on-acc: #2c1b06;
--color-tth14-on-acc: oklch(0.22 0.04 70);
--font-tth14-sans: 'Onest', system-ui, sans-serif;
--font-tth14-display: 'Familjen Grotesk', system-ui, sans-serif;
}
.tth-14 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(function () {
var magnets = document.querySelectorAll('[data-tth14-magnet]');
if (!magnets.length) return;
var reduced = matchMedia('(prefers-reduced-motion: reduce)');
var fine = matchMedia('(pointer: fine)');
magnets.forEach(function (el) {
var pull = parseFloat(el.getAttribute('data-pull')) || 0.35;
el.addEventListener('pointermove', function (e) {
if (reduced.matches || !fine.matches) return;
var r = el.getBoundingClientRect();
var x = (e.clientX - r.left - r.width / 2) * pull;
var y = (e.clientY - r.top - r.height / 2) * pull;
el.style.setProperty('--tth14-mx', x.toFixed(1) + 'px');
el.style.setProperty('--tth14-my', y.toFixed(1) + 'px');
});
el.addEventListener('pointerleave', function () {
el.style.setProperty('--tth14-mx', '0px');
el.style.setProperty('--tth14-my', '0px');
});
});
})();
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Magnetic Hover Effect for Buttons and Text
Source: https://codefronts.com/motion/tailwind-css-text-hover-effects/tailwind-magnetic-hover-effect-for-buttons-and-text/
Awwwards-style magnetic pills and a giant email link that lean toward the cursor. A tiny script writes --tth14-mx/--tth14-my; the markup reads them with translate-x-[var(--tth14-mx,0px)], and the label leans 55% further for parallax. Release snaps back on an elastic not-hover:ease-[cubic-bezier(.22,1.6,.36,1)].
## HTML
```html
<section class="tth-14 w-full min-h-svh grid place-items-center bg-tth14-bg bg-[radial-gradient(80%_60%_at_50%_40%,var(--color-tth14-glow),var(--color-tth14-bg)_75%)] px-5 py-14 font-tth14-sans text-tth14-ink" aria-label="Magnetic cursor tracking text demo">
<div class="flex flex-col items-center gap-[clamp(26px,5vw,44px)] text-center">
<p class="text-xs font-bold uppercase tracking-[0.26em] text-tth14-mut">Bureau Nocturne — digital practice, Toronto</p>
<div class="flex flex-wrap justify-center gap-[clamp(14px,3vw,26px)]">
<a id="tth14-top" href="#tth14-top" data-tth14-magnet data-pull="0.42" class="grid min-h-14 place-items-center rounded-full bg-tth14-acc px-[30px] text-base font-extrabold tracking-[0.01em] text-tth14-on-acc no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,box-shadow] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:shadow-[0_14px_40px_-10px_var(--color-tth14-acc)] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">View work</span></a>
<a href="#tth14-top" data-tth14-magnet data-pull="0.42" class="grid min-h-14 place-items-center rounded-full bg-tth14-acc px-[30px] text-base font-extrabold tracking-[0.01em] text-tth14-on-acc no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,box-shadow] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:shadow-[0_14px_40px_-10px_var(--color-tth14-acc)] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">Process</span></a>
<a href="#tth14-top" data-tth14-magnet data-pull="0.42" class="grid min-h-14 place-items-center rounded-full border-[1.5px] border-tth14-ink/35 px-[30px] text-base font-extrabold tracking-[0.01em] no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,border-color,background-color] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:border-tth14-ink hover:bg-tth14-ink/6 focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">About</span></a>
</div>
<a href="#tth14-top" data-tth14-magnet data-pull="0.2" class="group/mail inline-flex items-baseline gap-[0.35em] border-b-2 border-tth14-ink/25 pb-[0.12em] font-tth14-display text-[clamp(26px,5.6vw,52px)] font-semibold tracking-[-0.02em] no-underline translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)] transition-[translate,border-color] duration-150 ease-out not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)] hover:border-tth14-acc focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tth14-acc motion-reduce:transition-none"><span class="inline-block translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit]">hello@nocturne.studio</span><span aria-hidden="true" class="inline-block text-tth14-acc translate-x-[calc(var(--tth14-mx,0px)*.55)] translate-y-[calc(var(--tth14-my,0px)*.55)] [transition:inherit] group-hover/mail:rotate-45">↗</span></a>
<p class="max-w-[54ch] text-[12.5px] leading-[1.7] text-tth14-mut">Circle the cursor around a pill — it leans in, the label leans further, release snaps back on an elastic bezier. JS measures; Tailwind moves.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tth14-bg: #100e15;
--color-tth14-bg: oklch(0.16 0.01 290);
--color-tth14-glow: #1a1721;
--color-tth14-glow: oklch(0.21 0.017 295);
--color-tth14-ink: #f1eff5;
--color-tth14-ink: oklch(0.95 0.005 290);
--color-tth14-mut: #a29eaa;
--color-tth14-mut: oklch(0.72 0.015 290);
--color-tth14-acc: #f7a93b;
--color-tth14-acc: oklch(0.8 0.15 70);
--color-tth14-on-acc: #2c1b06;
--color-tth14-on-acc: oklch(0.22 0.04 70);
--font-tth14-sans: 'Onest', system-ui, sans-serif;
--font-tth14-display: 'Familjen Grotesk', system-ui, sans-serif;
}
.tth-14 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(function () {
var magnets = document.querySelectorAll('[data-tth14-magnet]');
if (!magnets.length) return;
var reduced = matchMedia('(prefers-reduced-motion: reduce)');
var fine = matchMedia('(pointer: fine)');
magnets.forEach(function (el) {
var pull = parseFloat(el.getAttribute('data-pull')) || 0.35;
el.addEventListener('pointermove', function (e) {
if (reduced.matches || !fine.matches) return;
var r = el.getBoundingClientRect();
var x = (e.clientX - r.left - r.width / 2) * pull;
var y = (e.clientY - r.top - r.height / 2) * pull;
el.style.setProperty('--tth14-mx', x.toFixed(1) + 'px');
el.style.setProperty('--tth14-my', y.toFixed(1) + 'px');
});
el.addEventListener('pointerleave', function () {
el.style.setProperty('--tth14-mx', '0px');
el.style.setProperty('--tth14-my', '0px');
});
});
})();
```How this works
Split the job. The script computes the pointer's offset from the element's centre, multiplied by data-pull, and writes it as two custom properties. Everything visual stays in the class list:
<a data-tth14-magnet data-pull="0.42" class="group/mag
translate-x-[var(--tth14-mx,0px)] translate-y-[var(--tth14-my,0px)]
transition-[translate] duration-150 ease-out
not-hover:duration-600 not-hover:ease-[cubic-bezier(.22,1.6,.36,1)]">Two curves. While hovered the follow is a quick 150ms ease-out so it tracks the pointer; once the pointer leaves, not-hover: swaps in a 600ms overshoot curve, so the reset to zero springs.
Parallax. The label inherits the transition and moves 1.55× as far as its pill, so the text appears to float above the surface.
Make it yours
- Pull strength:
data-pullper element (0.2 for big text, 0.4 for pills). - Stiffer spring: lower the overshoot from
1.6to1.2. - Magnetic icons: same markup on a 44px icon button.
Gotchas — read before shipping
- Always provide the
0pxfallback invar(), or the translate is invalid before the first move. - Never magnetise on touch — the script checks
(pointer: fine)and reduced motion. - Large pull values move the hit target away from the cursor; keep under 0.5.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Individual translate property — inside v4's floor.