Pure TailwindMIT licensed

Tailwind Circular Team Profile Avatar Hover

A comet-tail arc that spins only while hovered — not by adding the animation, but by flipping animation-play-state from paused to running, so the arc resumes from wherever it stopped. In utilities that is two arbitrary properties on a before: chain, and the rest of the ring is bg-conic-[…].

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="tih-16 w-full min-h-svh grid place-items-center bg-tih16-bg font-tih16-sans px-6 py-10" aria-label="Circular avatar hover with spinning ring demo">
  <div class="grid w-full max-w-195 gap-5 grid-cols-2 sm:grid-cols-4">
    <article style="--ring:oklch(0.75 0.16 300)" class="group flex flex-col items-center gap-2.5">
      <div class="relative w-20 h-20 overflow-hidden rounded-full before:content-[''] before:absolute before:-inset-[3px] before:rounded-full before:bg-conic-[from_0deg,transparent_0deg_270deg,var(--ring)_270deg_360deg] before:opacity-0 before:transition-opacity before:duration-300 before:animate-tih16-spin before:[animation-play-state:paused] group-hover:before:opacity-100 group-hover:before:[animation-play-state:running] motion-reduce:group-hover:before:[animation-play-state:paused]">
        <div class="relative z-2 grid place-items-center w-[74px] h-[74px] m-[3px] rounded-full text-[34px] bg-[image:url('https://i.pravatar.cc/400?img=5'),radial-gradient(circle_at_30%_30%,oklch(0.35_0.18_295),oklch(0.2_0.03_265))] bg-cover bg-center bg-no-repeat transition-transform duration-350 ease-tih16 group-hover:scale-106 motion-reduce:transition-none motion-reduce:group-hover:scale-100" aria-hidden="true"></div>
      </div>
      <p class="text-xs font-bold text-center text-tih16-text">Yuki Tanaka</p>
      <p class="-mt-2 text-[10px] text-center text-tih16-muted">Frontend Eng.</p>
      <div class="flex gap-1.5 opacity-0 translate-y-1.5 transition-[opacity,translate] duration-300 ease-tih16 group-hover:opacity-100 group-hover:translate-y-0 focus-within:opacity-100 focus-within:translate-y-0 motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">
        <a href="#yuki-x" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Yuki on X">&#120143;</a>
        <a href="#yuki-gh" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Yuki on GitHub">gh</a>
      </div>
    </article>
    <article style="--ring:oklch(0.8 0.14 163)" class="group flex flex-col items-center gap-2.5">
      <div class="relative w-20 h-20 overflow-hidden rounded-full before:content-[''] before:absolute before:-inset-[3px] before:rounded-full before:bg-conic-[from_0deg,transparent_0deg_270deg,var(--ring)_270deg_360deg] before:opacity-0 before:transition-opacity before:duration-300 before:animate-tih16-spin before:[animation-play-state:paused] group-hover:before:opacity-100 group-hover:before:[animation-play-state:running] motion-reduce:group-hover:before:[animation-play-state:paused]">
        <div class="relative z-2 grid place-items-center w-[74px] h-[74px] m-[3px] rounded-full text-[34px] bg-[image:url('https://i.pravatar.cc/400?img=14'),radial-gradient(circle_at_30%_30%,oklch(0.42_0.09_165),oklch(0.2_0.03_265))] bg-cover bg-center bg-no-repeat transition-transform duration-350 ease-tih16 group-hover:scale-106 motion-reduce:transition-none motion-reduce:group-hover:scale-100" aria-hidden="true"></div>
      </div>
      <p class="text-xs font-bold text-center text-tih16-text">Amara Diallo</p>
      <p class="-mt-2 text-[10px] text-center text-tih16-muted">UX Designer</p>
      <div class="flex gap-1.5 opacity-0 translate-y-1.5 transition-[opacity,translate] duration-300 ease-tih16 group-hover:opacity-100 group-hover:translate-y-0 focus-within:opacity-100 focus-within:translate-y-0 motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">
        <a href="#amara-x" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Amara on X">&#120143;</a>
        <a href="#amara-be" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Amara on Behance">be</a>
      </div>
    </article>
    <article style="--ring:oklch(0.76 0.16 55)" class="group flex flex-col items-center gap-2.5">
      <div class="relative w-20 h-20 overflow-hidden rounded-full before:content-[''] before:absolute before:-inset-[3px] before:rounded-full before:bg-conic-[from_0deg,transparent_0deg_270deg,var(--ring)_270deg_360deg] before:opacity-0 before:transition-opacity before:duration-300 before:animate-tih16-spin before:[animation-play-state:paused] group-hover:before:opacity-100 group-hover:before:[animation-play-state:running] motion-reduce:group-hover:before:[animation-play-state:paused]">
        <div class="relative z-2 grid place-items-center w-[74px] h-[74px] m-[3px] rounded-full text-[34px] bg-[image:url('https://i.pravatar.cc/400?img=60'),radial-gradient(circle_at_30%_30%,oklch(0.45_0.11_60),oklch(0.2_0.03_265))] bg-cover bg-center bg-no-repeat transition-transform duration-350 ease-tih16 group-hover:scale-106 motion-reduce:transition-none motion-reduce:group-hover:scale-100" aria-hidden="true"></div>
      </div>
      <p class="text-xs font-bold text-center text-tih16-text">Ravi Patel</p>
      <p class="-mt-2 text-[10px] text-center text-tih16-muted">Data Scientist</p>
      <div class="flex gap-1.5 opacity-0 translate-y-1.5 transition-[opacity,translate] duration-300 ease-tih16 group-hover:opacity-100 group-hover:translate-y-0 focus-within:opacity-100 focus-within:translate-y-0 motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">
        <a href="#ravi-x" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Ravi on X">&#120143;</a>
        <a href="#ravi-in" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Ravi on LinkedIn">in</a>
      </div>
    </article>
    <article style="--ring:oklch(0.74 0.14 250)" class="group flex flex-col items-center gap-2.5">
      <div class="relative w-20 h-20 overflow-hidden rounded-full before:content-[''] before:absolute before:-inset-[3px] before:rounded-full before:bg-conic-[from_0deg,transparent_0deg_270deg,var(--ring)_270deg_360deg] before:opacity-0 before:transition-opacity before:duration-300 before:animate-tih16-spin before:[animation-play-state:paused] group-hover:before:opacity-100 group-hover:before:[animation-play-state:running] motion-reduce:group-hover:before:[animation-play-state:paused]">
        <div class="relative z-2 grid place-items-center w-[74px] h-[74px] m-[3px] rounded-full text-[34px] bg-[image:url('https://i.pravatar.cc/400?img=9'),radial-gradient(circle_at_30%_30%,oklch(0.3_0.05_250),oklch(0.2_0.03_265))] bg-cover bg-center bg-no-repeat transition-transform duration-350 ease-tih16 group-hover:scale-106 motion-reduce:transition-none motion-reduce:group-hover:scale-100" aria-hidden="true"></div>
      </div>
      <p class="text-xs font-bold text-center text-tih16-text">Zoe Mercer</p>
      <p class="-mt-2 text-[10px] text-center text-tih16-muted">DevOps Lead</p>
      <div class="flex gap-1.5 opacity-0 translate-y-1.5 transition-[opacity,translate] duration-300 ease-tih16 group-hover:opacity-100 group-hover:translate-y-0 focus-within:opacity-100 focus-within:translate-y-0 motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">
        <a href="#zoe-gh" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Zoe on GitHub">gh</a>
        <a href="#zoe-in" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Zoe on LinkedIn">in</a>
      </div>
    </article>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tih16-bg: #080812;
  --color-tih16-bg: oklch(0.12 0.015 285);
  --color-tih16-text: #f1f5f9;
  --color-tih16-text: oklch(0.96 0.005 250);
  --color-tih16-muted: #64748b;
  --color-tih16-muted: oklch(0.58 0.035 255);
  --color-tih16-acc: #a78bfa;
  --color-tih16-acc: oklch(0.75 0.16 300);
  --font-tih16-sans: 'Space Grotesk', system-ui, sans-serif;
  --ease-tih16: cubic-bezier(0.34, 1.56, 0.64, 1);
  --animate-tih16-spin: tih16-spin 1.2s linear infinite;
}

@keyframes tih16-spin {
  to {
    rotate: 360deg;
  }
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
   w-full min-h-svh, so the stage is correctly sized before the browser build compiles. */

.tih-16 {
  width: 100%;
  min-height: 100svh;
}

.tih-16 ::selection {
  background: oklch(0.75 0.16 300);
  color: #000;
}
/* @supports has no variant form: engines without conic-gradient get a solid ring. */

@supports not (background: conic-gradient(red, blue)) {
  .tih-16 .group > div::before {
    background: var(--ring);
  }
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<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.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tih16-bg: #080812;
  --color-tih16-bg: oklch(0.12 0.015 285);
  --color-tih16-text: #f1f5f9;
  --color-tih16-text: oklch(0.96 0.005 250);
  --color-tih16-muted: #64748b;
  --color-tih16-muted: oklch(0.58 0.035 255);
  --color-tih16-acc: #a78bfa;
  --color-tih16-acc: oklch(0.75 0.16 300);
  --font-tih16-sans: 'Space Grotesk', system-ui, sans-serif;
  --ease-tih16: cubic-bezier(0.34, 1.56, 0.64, 1);
  --animate-tih16-spin: tih16-spin 1.2s linear infinite;
}

@keyframes tih16-spin {
  to {
    rotate: 360deg;
  }
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
   w-full min-h-svh, so the stage is correctly sized before the browser build compiles. */

.tih-16 {
  width: 100%;
  min-height: 100svh;
}

.tih-16 ::selection {
  background: oklch(0.75 0.16 300);
  color: #000;
}
/* @supports has no variant form: engines without conic-gradient get a solid ring. */

@supports not (background: conic-gradient(red, blue)) {
  .tih-16 .group > div::before {
    background: var(--ring);
  }
}
3Fonts used by this demooptional — skip if you have your own type stack
<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=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • animation-play-state has no utility and should not — it is a state machine value, not a design token. Both halves stay arbitrary properties, and the important part is that they sit adjacent on the element.
  • The conic arc becomes before:bg-conic-[…], reading a per-card --ring, so four :nth-child colour rules disappear.
  • The keyframe animates rotate rather than transform: rotate(), matching what v4's own utilities set.
  • focus-within: is added to the social row: it was hover-only in the CSS original, which made the links focusable but invisible.
  • motion-reduce: shows the ring and pauses it — the arc still communicates hover, with no rotation.
  • The @supports fallback stays in CSS. Feature queries are the other category with no variant form, alongside keyframes.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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 Circular Team Profile Avatar Hover
Source: https://codefronts.com/motion/tailwind-css-image-hover-effects/tailwind-circular-team-profile-avatar-hover/

A comet-tail arc that spins only while hovered &mdash; not by adding the animation, but by flipping animation-play-state from paused to running, so the arc resumes from wherever it stopped. In utilities that is two arbitrary properties on a before: chain, and the rest of the ring is bg-conic-[…].
## HTML
```html
<section class="tih-16 w-full min-h-svh grid place-items-center bg-tih16-bg font-tih16-sans px-6 py-10" aria-label="Circular avatar hover with spinning ring demo">
  <div class="grid w-full max-w-195 gap-5 grid-cols-2 sm:grid-cols-4">
    <article style="--ring:oklch(0.75 0.16 300)" class="group flex flex-col items-center gap-2.5">
      <div class="relative w-20 h-20 overflow-hidden rounded-full before:content-[''] before:absolute before:-inset-[3px] before:rounded-full before:bg-conic-[from_0deg,transparent_0deg_270deg,var(--ring)_270deg_360deg] before:opacity-0 before:transition-opacity before:duration-300 before:animate-tih16-spin before:[animation-play-state:paused] group-hover:before:opacity-100 group-hover:before:[animation-play-state:running] motion-reduce:group-hover:before:[animation-play-state:paused]">
        <div class="relative z-2 grid place-items-center w-[74px] h-[74px] m-[3px] rounded-full text-[34px] bg-[image:url('https://i.pravatar.cc/400?img=5'),radial-gradient(circle_at_30%_30%,oklch(0.35_0.18_295),oklch(0.2_0.03_265))] bg-cover bg-center bg-no-repeat transition-transform duration-350 ease-tih16 group-hover:scale-106 motion-reduce:transition-none motion-reduce:group-hover:scale-100" aria-hidden="true"></div>
      </div>
      <p class="text-xs font-bold text-center text-tih16-text">Yuki Tanaka</p>
      <p class="-mt-2 text-[10px] text-center text-tih16-muted">Frontend Eng.</p>
      <div class="flex gap-1.5 opacity-0 translate-y-1.5 transition-[opacity,translate] duration-300 ease-tih16 group-hover:opacity-100 group-hover:translate-y-0 focus-within:opacity-100 focus-within:translate-y-0 motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">
        <a href="#yuki-x" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Yuki on X">&#120143;</a>
        <a href="#yuki-gh" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Yuki on GitHub">gh</a>
      </div>
    </article>
    <article style="--ring:oklch(0.8 0.14 163)" class="group flex flex-col items-center gap-2.5">
      <div class="relative w-20 h-20 overflow-hidden rounded-full before:content-[''] before:absolute before:-inset-[3px] before:rounded-full before:bg-conic-[from_0deg,transparent_0deg_270deg,var(--ring)_270deg_360deg] before:opacity-0 before:transition-opacity before:duration-300 before:animate-tih16-spin before:[animation-play-state:paused] group-hover:before:opacity-100 group-hover:before:[animation-play-state:running] motion-reduce:group-hover:before:[animation-play-state:paused]">
        <div class="relative z-2 grid place-items-center w-[74px] h-[74px] m-[3px] rounded-full text-[34px] bg-[image:url('https://i.pravatar.cc/400?img=14'),radial-gradient(circle_at_30%_30%,oklch(0.42_0.09_165),oklch(0.2_0.03_265))] bg-cover bg-center bg-no-repeat transition-transform duration-350 ease-tih16 group-hover:scale-106 motion-reduce:transition-none motion-reduce:group-hover:scale-100" aria-hidden="true"></div>
      </div>
      <p class="text-xs font-bold text-center text-tih16-text">Amara Diallo</p>
      <p class="-mt-2 text-[10px] text-center text-tih16-muted">UX Designer</p>
      <div class="flex gap-1.5 opacity-0 translate-y-1.5 transition-[opacity,translate] duration-300 ease-tih16 group-hover:opacity-100 group-hover:translate-y-0 focus-within:opacity-100 focus-within:translate-y-0 motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">
        <a href="#amara-x" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Amara on X">&#120143;</a>
        <a href="#amara-be" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Amara on Behance">be</a>
      </div>
    </article>
    <article style="--ring:oklch(0.76 0.16 55)" class="group flex flex-col items-center gap-2.5">
      <div class="relative w-20 h-20 overflow-hidden rounded-full before:content-[''] before:absolute before:-inset-[3px] before:rounded-full before:bg-conic-[from_0deg,transparent_0deg_270deg,var(--ring)_270deg_360deg] before:opacity-0 before:transition-opacity before:duration-300 before:animate-tih16-spin before:[animation-play-state:paused] group-hover:before:opacity-100 group-hover:before:[animation-play-state:running] motion-reduce:group-hover:before:[animation-play-state:paused]">
        <div class="relative z-2 grid place-items-center w-[74px] h-[74px] m-[3px] rounded-full text-[34px] bg-[image:url('https://i.pravatar.cc/400?img=60'),radial-gradient(circle_at_30%_30%,oklch(0.45_0.11_60),oklch(0.2_0.03_265))] bg-cover bg-center bg-no-repeat transition-transform duration-350 ease-tih16 group-hover:scale-106 motion-reduce:transition-none motion-reduce:group-hover:scale-100" aria-hidden="true"></div>
      </div>
      <p class="text-xs font-bold text-center text-tih16-text">Ravi Patel</p>
      <p class="-mt-2 text-[10px] text-center text-tih16-muted">Data Scientist</p>
      <div class="flex gap-1.5 opacity-0 translate-y-1.5 transition-[opacity,translate] duration-300 ease-tih16 group-hover:opacity-100 group-hover:translate-y-0 focus-within:opacity-100 focus-within:translate-y-0 motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">
        <a href="#ravi-x" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Ravi on X">&#120143;</a>
        <a href="#ravi-in" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Ravi on LinkedIn">in</a>
      </div>
    </article>
    <article style="--ring:oklch(0.74 0.14 250)" class="group flex flex-col items-center gap-2.5">
      <div class="relative w-20 h-20 overflow-hidden rounded-full before:content-[''] before:absolute before:-inset-[3px] before:rounded-full before:bg-conic-[from_0deg,transparent_0deg_270deg,var(--ring)_270deg_360deg] before:opacity-0 before:transition-opacity before:duration-300 before:animate-tih16-spin before:[animation-play-state:paused] group-hover:before:opacity-100 group-hover:before:[animation-play-state:running] motion-reduce:group-hover:before:[animation-play-state:paused]">
        <div class="relative z-2 grid place-items-center w-[74px] h-[74px] m-[3px] rounded-full text-[34px] bg-[image:url('https://i.pravatar.cc/400?img=9'),radial-gradient(circle_at_30%_30%,oklch(0.3_0.05_250),oklch(0.2_0.03_265))] bg-cover bg-center bg-no-repeat transition-transform duration-350 ease-tih16 group-hover:scale-106 motion-reduce:transition-none motion-reduce:group-hover:scale-100" aria-hidden="true"></div>
      </div>
      <p class="text-xs font-bold text-center text-tih16-text">Zoe Mercer</p>
      <p class="-mt-2 text-[10px] text-center text-tih16-muted">DevOps Lead</p>
      <div class="flex gap-1.5 opacity-0 translate-y-1.5 transition-[opacity,translate] duration-300 ease-tih16 group-hover:opacity-100 group-hover:translate-y-0 focus-within:opacity-100 focus-within:translate-y-0 motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">
        <a href="#zoe-gh" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Zoe on GitHub">gh</a>
        <a href="#zoe-in" class="px-1.25 py-0.5 rounded text-xs text-tih16-muted no-underline bg-[oklch(1_0_0/0.07)] focus-visible:outline-2 focus-visible:outline-tih16-acc focus-visible:outline-offset-2" aria-label="Zoe on LinkedIn">in</a>
      </div>
    </article>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tih16-bg: #080812;
  --color-tih16-bg: oklch(0.12 0.015 285);
  --color-tih16-text: #f1f5f9;
  --color-tih16-text: oklch(0.96 0.005 250);
  --color-tih16-muted: #64748b;
  --color-tih16-muted: oklch(0.58 0.035 255);
  --color-tih16-acc: #a78bfa;
  --color-tih16-acc: oklch(0.75 0.16 300);
  --font-tih16-sans: 'Space Grotesk', system-ui, sans-serif;
  --ease-tih16: cubic-bezier(0.34, 1.56, 0.64, 1);
  --animate-tih16-spin: tih16-spin 1.2s linear infinite;
}

@keyframes tih16-spin {
  to {
    rotate: 360deg;
  }
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
   w-full min-h-svh, so the stage is correctly sized before the browser build compiles. */

.tih-16 {
  width: 100%;
  min-height: 100svh;
}

.tih-16 ::selection {
  background: oklch(0.75 0.16 300);
  color: #000;
}
/* @supports has no variant form: engines without conic-gradient get a solid ring. */

@supports not (background: conic-gradient(red, blue)) {
  .tih-16 .group > div::before {
    background: var(--ring);
  }
}
```

How this works

Play-state, not presence. The animation is always declared and always paused:

class="before:animate-tih16-spin
   before:[animation-play-state:paused]
   before:opacity-0
   group-hover:before:opacity-100
   group-hover:before:[animation-play-state:running]"

This is the opposite trade-off from demo 15, and deliberately so: pausing keeps the arc's rotation continuous across hovers, where re-adding an animation restarts it from 0deg. Both are correct — a ripple should restart, a spinner should not.

The arc is a gradient, not a border. conic-gradient with a transparent 0–270deg span and colour from 270–360deg gives a quarter arc; the avatar sits on top at z-2 with a 3px margin, so only the ring shows around it.

Per-card colour without per-card rules. Each item sets --ring inline; the conic utility reads it. Four :nth-child rules disappear.

Make it yours

  • Arc length: the 270deg stop — 180deg is a half ring, 330deg a short comet.
  • Spin speed: the duration inside --animate-tih16-spin.
  • Solid ring: set both stops to var(--ring).
  • Counter-rotation: add before:[animation-direction:reverse] to alternate cards.
  • Resting placeholder: before:opacity-30 instead of 0 shows a dim ring before hover.

Gotchas — read before shipping

  • The animation must be declared at rest for animation-play-state to have anything to pause. Declaring it only under group-hover: makes the pause meaningless.
  • conic-gradient needs Firefox 83+; the @supports fallback that swaps in a solid border stays in the stylesheet, because @supports has no variant form.
  • Safari can bleed 1–2px of the gradient past a rounded-full clip; the wrapper's overflow-hidden is the fix and must not be removed.
  • before:content-[''] is required even for a purely decorative ring.
  • Stacked variants read outside-in: group-hover:before: is &ldquo;the before of this element, while the group is hovered&rdquo; — not the reverse.
  • The browser CDN build compiles in the page and is for prototyping only. Ship the CLI or a plugin build.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

conic-gradient is Chrome 69+, Safari 12.1+, Firefox 83+; the stylesheet carries an @supports solid-border fallback for older engines.

Techniques used in this demo

Theme tokensset in @theme, used by a utility

Arbitraryneeds bracket syntax

Plain CSSno Tailwind surface

Search CodeFronts

Loading…