24 CSS Animated Cards18 / 24
Animated Gradient Border Card (Conic Rotate)
The rotating neon outline, done three ways so you can pick your compatibility floor: a registered @property angle sweeping a conic gradient, a mask-composite ring that keeps the card background transparent, and a padding-box fallback. Includes the glow-bloom variant where a blurred copy of the border sits behind the card.
Published
The code
<section class="ac-18" aria-label="Animated conic gradient border card demo">
<div class="ac-18__stage">
<div class="ac-18__row">
<article class="ac-18__card ac-18__card--solid">
<span class="ac-18__bloom" aria-hidden="true"></span>
<p class="ac-18__eyebrow">Conic sweep</p>
<h3 class="ac-18__title">Registered angle</h3>
<p class="ac-18__copy">A single <code>@property <angle></code> animated 0 → 360deg behind an opaque card. Cheapest possible route.</p>
<p class="ac-18__meta">5 s linear · hover to accelerate</p>
</article>
<article class="ac-18__card ac-18__card--mask">
<p class="ac-18__eyebrow">Mask composite</p>
<h3 class="ac-18__title">True ring</h3>
<p class="ac-18__copy">Two masks subtracted leave a real 2 px ring, so the card behind it can stay glassy and transparent.</p>
<p class="ac-18__meta">mask-composite: exclude</p>
</article>
<article class="ac-18__card ac-18__card--comet">
<p class="ac-18__eyebrow">Partial arc</p>
<h3 class="ac-18__title">Comet trail</h3>
<p class="ac-18__copy">Transparent stops turn the full sweep into a single travelling highlight — a status indicator that never sits still.</p>
<p class="ac-18__meta">3.2 s linear · transparent stops</p>
</article>
</div>
<p class="ac-18__chip" aria-hidden="true">@property <angle> · conic-gradient(from var(--a)) · mask-composite ring · blurred bloom copy · seam-free stop list</p>
</div>
</section><section class="ac-18" aria-label="Animated conic gradient border card demo">
<div class="ac-18__stage">
<div class="ac-18__row">
<article class="ac-18__card ac-18__card--solid">
<span class="ac-18__bloom" aria-hidden="true"></span>
<p class="ac-18__eyebrow">Conic sweep</p>
<h3 class="ac-18__title">Registered angle</h3>
<p class="ac-18__copy">A single <code>@property <angle></code> animated 0 → 360deg behind an opaque card. Cheapest possible route.</p>
<p class="ac-18__meta">5 s linear · hover to accelerate</p>
</article>
<article class="ac-18__card ac-18__card--mask">
<p class="ac-18__eyebrow">Mask composite</p>
<h3 class="ac-18__title">True ring</h3>
<p class="ac-18__copy">Two masks subtracted leave a real 2 px ring, so the card behind it can stay glassy and transparent.</p>
<p class="ac-18__meta">mask-composite: exclude</p>
</article>
<article class="ac-18__card ac-18__card--comet">
<p class="ac-18__eyebrow">Partial arc</p>
<h3 class="ac-18__title">Comet trail</h3>
<p class="ac-18__copy">Transparent stops turn the full sweep into a single travelling highlight — a status indicator that never sits still.</p>
<p class="ac-18__meta">3.2 s linear · transparent stops</p>
</article>
</div>
<p class="ac-18__chip" aria-hidden="true">@property <angle> · conic-gradient(from var(--a)) · mask-composite ring · blurred bloom copy · seam-free stop list</p>
</div>
</section>@property --ac-18-a {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.ac-18,
.ac-18 *,
.ac-18 *::before,
.ac-18 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ac-18 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
--ac-18-bg: oklch(0.14 0.016 275);
--ac-18-card: oklch(0.19 0.018 275);
--ac-18-ink: oklch(0.97 0.004 280);
--ac-18-mut: oklch(0.70 0.02 280);
--ac-18-1: oklch(0.78 0.19 300);
--ac-18-2: oklch(0.82 0.16 200);
--ac-18-3: oklch(0.85 0.17 88);
background: var(--ac-18-bg);
color: var(--ac-18-ink);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
-webkit-font-smoothing: antialiased;
}
.ac-18__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: 20px;
padding: clamp(20px,5vw,58px);
}
.ac-18__row {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(224px,1fr));
gap: 18px;
width: min(100%,880px);
}
.ac-18__card {
position: relative;
display: grid;
gap: 9px;
padding: 24px 22px;
border-radius: 20px;
background: var(--ac-18-card);
isolation: isolate;
}
.ac-18__card::before {
content: '';
position: absolute;
inset: -2px;
z-index: -2;
border-radius: 22px;
background: conic-gradient(from var(--ac-18-a),var(--ac-18-1),var(--ac-18-2),var(--ac-18-3),var(--ac-18-1));
animation: ac-18-spin 5s linear infinite;
}
.ac-18__card:hover::before {
animation-duration: 1.6s;
}
@keyframes ac-18-spin {
to {
--ac-18-a: 360deg;
}
}
.ac-18__card--solid::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: 20px;
background: var(--ac-18-card);
}
.ac-18__bloom {
position: absolute;
inset: -6px;
z-index: -3;
border-radius: 26px;
background: conic-gradient(from var(--ac-18-a),var(--ac-18-1),var(--ac-18-2),var(--ac-18-3),var(--ac-18-1));
filter: blur(16px);
opacity: .55;
animation: ac-18-spin 5s linear infinite;
}
.ac-18__card--mask {
background: oklch(1 0 0/.04);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.ac-18__card--mask::before {
inset: 0;
padding: 2px;
border-radius: 20px;
background: conic-gradient(from var(--ac-18-a),var(--ac-18-1),var(--ac-18-2),var(--ac-18-3),var(--ac-18-1));
-webkit-mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
animation-duration: 6.5s;
animation-direction: reverse;
}
.ac-18__card--comet::before {
background: conic-gradient(from var(--ac-18-a),transparent 0 62%,var(--ac-18-2) 78%,var(--ac-18-1) 92%,transparent 100%);
animation-duration: 3.2s;
}
.ac-18__card--comet::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: 20px;
background: var(--ac-18-card);
}
.ac-18__eyebrow {
font-size: 11px;
font-weight: 700;
letter-spacing: .17em;
text-transform: uppercase;
color: var(--ac-18-2);
}
.ac-18__title {
font-size: 20px;
font-weight: 660;
letter-spacing: -.03em;
}
.ac-18__copy {
font-size: 13.5px;
line-height: 1.55;
color: var(--ac-18-mut);
text-wrap: pretty;
}
.ac-18__copy code {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
background: oklch(1 0 0/.08);
padding: 2px 5px;
border-radius: 5px;
}
.ac-18__meta {
margin-top: 4px;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--ac-18-mut);
}
.ac-18__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
line-height: 1.6;
color: var(--ac-18-mut);
padding: 8px 15px;
border: 1px solid oklch(1 0 0/.13);
border-radius: 99px;
text-align: center;
max-width: min(100%,780px);
}
@supports not (background: conic-gradient(from var(--ac-18-a),red,blue)) {
.ac-18__card::before,
.ac-18__bloom {
animation: none;
background: linear-gradient(120deg,var(--ac-18-1),var(--ac-18-2),var(--ac-18-3));
}
}
@media (prefers-reduced-motion: reduce) {
.ac-18__card::before,
.ac-18__bloom {
animation: none;
}
.ac-18__card:hover::before {
animation: none;
}
}@property --ac-18-a {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.ac-18,
.ac-18 *,
.ac-18 *::before,
.ac-18 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ac-18 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
--ac-18-bg: oklch(0.14 0.016 275);
--ac-18-card: oklch(0.19 0.018 275);
--ac-18-ink: oklch(0.97 0.004 280);
--ac-18-mut: oklch(0.70 0.02 280);
--ac-18-1: oklch(0.78 0.19 300);
--ac-18-2: oklch(0.82 0.16 200);
--ac-18-3: oklch(0.85 0.17 88);
background: var(--ac-18-bg);
color: var(--ac-18-ink);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
-webkit-font-smoothing: antialiased;
}
.ac-18__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: 20px;
padding: clamp(20px,5vw,58px);
}
.ac-18__row {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(224px,1fr));
gap: 18px;
width: min(100%,880px);
}
.ac-18__card {
position: relative;
display: grid;
gap: 9px;
padding: 24px 22px;
border-radius: 20px;
background: var(--ac-18-card);
isolation: isolate;
}
.ac-18__card::before {
content: '';
position: absolute;
inset: -2px;
z-index: -2;
border-radius: 22px;
background: conic-gradient(from var(--ac-18-a),var(--ac-18-1),var(--ac-18-2),var(--ac-18-3),var(--ac-18-1));
animation: ac-18-spin 5s linear infinite;
}
.ac-18__card:hover::before {
animation-duration: 1.6s;
}
@keyframes ac-18-spin {
to {
--ac-18-a: 360deg;
}
}
.ac-18__card--solid::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: 20px;
background: var(--ac-18-card);
}
.ac-18__bloom {
position: absolute;
inset: -6px;
z-index: -3;
border-radius: 26px;
background: conic-gradient(from var(--ac-18-a),var(--ac-18-1),var(--ac-18-2),var(--ac-18-3),var(--ac-18-1));
filter: blur(16px);
opacity: .55;
animation: ac-18-spin 5s linear infinite;
}
.ac-18__card--mask {
background: oklch(1 0 0/.04);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.ac-18__card--mask::before {
inset: 0;
padding: 2px;
border-radius: 20px;
background: conic-gradient(from var(--ac-18-a),var(--ac-18-1),var(--ac-18-2),var(--ac-18-3),var(--ac-18-1));
-webkit-mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
animation-duration: 6.5s;
animation-direction: reverse;
}
.ac-18__card--comet::before {
background: conic-gradient(from var(--ac-18-a),transparent 0 62%,var(--ac-18-2) 78%,var(--ac-18-1) 92%,transparent 100%);
animation-duration: 3.2s;
}
.ac-18__card--comet::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: 20px;
background: var(--ac-18-card);
}
.ac-18__eyebrow {
font-size: 11px;
font-weight: 700;
letter-spacing: .17em;
text-transform: uppercase;
color: var(--ac-18-2);
}
.ac-18__title {
font-size: 20px;
font-weight: 660;
letter-spacing: -.03em;
}
.ac-18__copy {
font-size: 13.5px;
line-height: 1.55;
color: var(--ac-18-mut);
text-wrap: pretty;
}
.ac-18__copy code {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
background: oklch(1 0 0/.08);
padding: 2px 5px;
border-radius: 5px;
}
.ac-18__meta {
margin-top: 4px;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--ac-18-mut);
}
.ac-18__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
line-height: 1.6;
color: var(--ac-18-mut);
padding: 8px 15px;
border: 1px solid oklch(1 0 0/.13);
border-radius: 99px;
text-align: center;
max-width: min(100%,780px);
}
@supports not (background: conic-gradient(from var(--ac-18-a),red,blue)) {
.ac-18__card::before,
.ac-18__bloom {
animation: none;
background: linear-gradient(120deg,var(--ac-18-1),var(--ac-18-2),var(--ac-18-3));
}
}
@media (prefers-reduced-motion: reduce) {
.ac-18__card::before,
.ac-18__bloom {
animation: none;
}
.ac-18__card:hover::before {
animation: none;
}
}Here's a working CSS Animated Card 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: Animated Gradient Border Card (Conic Rotate)
Source: https://codefronts.com/components/css-animated-cards/animated-gradient-border-card-conic-rotate/
The rotating neon outline, done three ways so you can pick your compatibility floor: a registered @property angle sweeping a conic gradient, a mask-composite ring that keeps the card background transparent, and a padding-box fallback. Includes the glow-bloom variant where a blurred copy of the border sits behind the card.
## HTML
```html
<section class="ac-18" aria-label="Animated conic gradient border card demo">
<div class="ac-18__stage">
<div class="ac-18__row">
<article class="ac-18__card ac-18__card--solid">
<span class="ac-18__bloom" aria-hidden="true"></span>
<p class="ac-18__eyebrow">Conic sweep</p>
<h3 class="ac-18__title">Registered angle</h3>
<p class="ac-18__copy">A single <code>@property <angle></code> animated 0 → 360deg behind an opaque card. Cheapest possible route.</p>
<p class="ac-18__meta">5 s linear · hover to accelerate</p>
</article>
<article class="ac-18__card ac-18__card--mask">
<p class="ac-18__eyebrow">Mask composite</p>
<h3 class="ac-18__title">True ring</h3>
<p class="ac-18__copy">Two masks subtracted leave a real 2 px ring, so the card behind it can stay glassy and transparent.</p>
<p class="ac-18__meta">mask-composite: exclude</p>
</article>
<article class="ac-18__card ac-18__card--comet">
<p class="ac-18__eyebrow">Partial arc</p>
<h3 class="ac-18__title">Comet trail</h3>
<p class="ac-18__copy">Transparent stops turn the full sweep into a single travelling highlight — a status indicator that never sits still.</p>
<p class="ac-18__meta">3.2 s linear · transparent stops</p>
</article>
</div>
<p class="ac-18__chip" aria-hidden="true">@property <angle> · conic-gradient(from var(--a)) · mask-composite ring · blurred bloom copy · seam-free stop list</p>
</div>
</section>
```
## CSS
```css
@property --ac-18-a {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.ac-18,
.ac-18 *,
.ac-18 *::before,
.ac-18 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ac-18 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
--ac-18-bg: oklch(0.14 0.016 275);
--ac-18-card: oklch(0.19 0.018 275);
--ac-18-ink: oklch(0.97 0.004 280);
--ac-18-mut: oklch(0.70 0.02 280);
--ac-18-1: oklch(0.78 0.19 300);
--ac-18-2: oklch(0.82 0.16 200);
--ac-18-3: oklch(0.85 0.17 88);
background: var(--ac-18-bg);
color: var(--ac-18-ink);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
-webkit-font-smoothing: antialiased;
}
.ac-18__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: 20px;
padding: clamp(20px,5vw,58px);
}
.ac-18__row {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(224px,1fr));
gap: 18px;
width: min(100%,880px);
}
.ac-18__card {
position: relative;
display: grid;
gap: 9px;
padding: 24px 22px;
border-radius: 20px;
background: var(--ac-18-card);
isolation: isolate;
}
.ac-18__card::before {
content: '';
position: absolute;
inset: -2px;
z-index: -2;
border-radius: 22px;
background: conic-gradient(from var(--ac-18-a),var(--ac-18-1),var(--ac-18-2),var(--ac-18-3),var(--ac-18-1));
animation: ac-18-spin 5s linear infinite;
}
.ac-18__card:hover::before {
animation-duration: 1.6s;
}
@keyframes ac-18-spin {
to {
--ac-18-a: 360deg;
}
}
.ac-18__card--solid::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: 20px;
background: var(--ac-18-card);
}
.ac-18__bloom {
position: absolute;
inset: -6px;
z-index: -3;
border-radius: 26px;
background: conic-gradient(from var(--ac-18-a),var(--ac-18-1),var(--ac-18-2),var(--ac-18-3),var(--ac-18-1));
filter: blur(16px);
opacity: .55;
animation: ac-18-spin 5s linear infinite;
}
.ac-18__card--mask {
background: oklch(1 0 0/.04);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.ac-18__card--mask::before {
inset: 0;
padding: 2px;
border-radius: 20px;
background: conic-gradient(from var(--ac-18-a),var(--ac-18-1),var(--ac-18-2),var(--ac-18-3),var(--ac-18-1));
-webkit-mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
animation-duration: 6.5s;
animation-direction: reverse;
}
.ac-18__card--comet::before {
background: conic-gradient(from var(--ac-18-a),transparent 0 62%,var(--ac-18-2) 78%,var(--ac-18-1) 92%,transparent 100%);
animation-duration: 3.2s;
}
.ac-18__card--comet::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: 20px;
background: var(--ac-18-card);
}
.ac-18__eyebrow {
font-size: 11px;
font-weight: 700;
letter-spacing: .17em;
text-transform: uppercase;
color: var(--ac-18-2);
}
.ac-18__title {
font-size: 20px;
font-weight: 660;
letter-spacing: -.03em;
}
.ac-18__copy {
font-size: 13.5px;
line-height: 1.55;
color: var(--ac-18-mut);
text-wrap: pretty;
}
.ac-18__copy code {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
background: oklch(1 0 0/.08);
padding: 2px 5px;
border-radius: 5px;
}
.ac-18__meta {
margin-top: 4px;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--ac-18-mut);
}
.ac-18__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
line-height: 1.6;
color: var(--ac-18-mut);
padding: 8px 15px;
border: 1px solid oklch(1 0 0/.13);
border-radius: 99px;
text-align: center;
max-width: min(100%,780px);
}
@supports not (background: conic-gradient(from var(--ac-18-a),red,blue)) {
.ac-18__card::before,
.ac-18__bloom {
animation: none;
background: linear-gradient(120deg,var(--ac-18-1),var(--ac-18-2),var(--ac-18-3));
}
}
@media (prefers-reduced-motion: reduce) {
.ac-18__card::before,
.ac-18__bloom {
animation: none;
}
.ac-18__card:hover::before {
animation: none;
}
}
```Here's a working CSS Animated Card 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: Animated Gradient Border Card (Conic Rotate)
Source: https://codefronts.com/components/css-animated-cards/animated-gradient-border-card-conic-rotate/
The rotating neon outline, done three ways so you can pick your compatibility floor: a registered @property angle sweeping a conic gradient, a mask-composite ring that keeps the card background transparent, and a padding-box fallback. Includes the glow-bloom variant where a blurred copy of the border sits behind the card.
## HTML
```html
<section class="ac-18" aria-label="Animated conic gradient border card demo">
<div class="ac-18__stage">
<div class="ac-18__row">
<article class="ac-18__card ac-18__card--solid">
<span class="ac-18__bloom" aria-hidden="true"></span>
<p class="ac-18__eyebrow">Conic sweep</p>
<h3 class="ac-18__title">Registered angle</h3>
<p class="ac-18__copy">A single <code>@property <angle></code> animated 0 → 360deg behind an opaque card. Cheapest possible route.</p>
<p class="ac-18__meta">5 s linear · hover to accelerate</p>
</article>
<article class="ac-18__card ac-18__card--mask">
<p class="ac-18__eyebrow">Mask composite</p>
<h3 class="ac-18__title">True ring</h3>
<p class="ac-18__copy">Two masks subtracted leave a real 2 px ring, so the card behind it can stay glassy and transparent.</p>
<p class="ac-18__meta">mask-composite: exclude</p>
</article>
<article class="ac-18__card ac-18__card--comet">
<p class="ac-18__eyebrow">Partial arc</p>
<h3 class="ac-18__title">Comet trail</h3>
<p class="ac-18__copy">Transparent stops turn the full sweep into a single travelling highlight — a status indicator that never sits still.</p>
<p class="ac-18__meta">3.2 s linear · transparent stops</p>
</article>
</div>
<p class="ac-18__chip" aria-hidden="true">@property <angle> · conic-gradient(from var(--a)) · mask-composite ring · blurred bloom copy · seam-free stop list</p>
</div>
</section>
```
## CSS
```css
@property --ac-18-a {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.ac-18,
.ac-18 *,
.ac-18 *::before,
.ac-18 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ac-18 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
--ac-18-bg: oklch(0.14 0.016 275);
--ac-18-card: oklch(0.19 0.018 275);
--ac-18-ink: oklch(0.97 0.004 280);
--ac-18-mut: oklch(0.70 0.02 280);
--ac-18-1: oklch(0.78 0.19 300);
--ac-18-2: oklch(0.82 0.16 200);
--ac-18-3: oklch(0.85 0.17 88);
background: var(--ac-18-bg);
color: var(--ac-18-ink);
font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
-webkit-font-smoothing: antialiased;
}
.ac-18__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: 20px;
padding: clamp(20px,5vw,58px);
}
.ac-18__row {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(224px,1fr));
gap: 18px;
width: min(100%,880px);
}
.ac-18__card {
position: relative;
display: grid;
gap: 9px;
padding: 24px 22px;
border-radius: 20px;
background: var(--ac-18-card);
isolation: isolate;
}
.ac-18__card::before {
content: '';
position: absolute;
inset: -2px;
z-index: -2;
border-radius: 22px;
background: conic-gradient(from var(--ac-18-a),var(--ac-18-1),var(--ac-18-2),var(--ac-18-3),var(--ac-18-1));
animation: ac-18-spin 5s linear infinite;
}
.ac-18__card:hover::before {
animation-duration: 1.6s;
}
@keyframes ac-18-spin {
to {
--ac-18-a: 360deg;
}
}
.ac-18__card--solid::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: 20px;
background: var(--ac-18-card);
}
.ac-18__bloom {
position: absolute;
inset: -6px;
z-index: -3;
border-radius: 26px;
background: conic-gradient(from var(--ac-18-a),var(--ac-18-1),var(--ac-18-2),var(--ac-18-3),var(--ac-18-1));
filter: blur(16px);
opacity: .55;
animation: ac-18-spin 5s linear infinite;
}
.ac-18__card--mask {
background: oklch(1 0 0/.04);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.ac-18__card--mask::before {
inset: 0;
padding: 2px;
border-radius: 20px;
background: conic-gradient(from var(--ac-18-a),var(--ac-18-1),var(--ac-18-2),var(--ac-18-3),var(--ac-18-1));
-webkit-mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
animation-duration: 6.5s;
animation-direction: reverse;
}
.ac-18__card--comet::before {
background: conic-gradient(from var(--ac-18-a),transparent 0 62%,var(--ac-18-2) 78%,var(--ac-18-1) 92%,transparent 100%);
animation-duration: 3.2s;
}
.ac-18__card--comet::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: 20px;
background: var(--ac-18-card);
}
.ac-18__eyebrow {
font-size: 11px;
font-weight: 700;
letter-spacing: .17em;
text-transform: uppercase;
color: var(--ac-18-2);
}
.ac-18__title {
font-size: 20px;
font-weight: 660;
letter-spacing: -.03em;
}
.ac-18__copy {
font-size: 13.5px;
line-height: 1.55;
color: var(--ac-18-mut);
text-wrap: pretty;
}
.ac-18__copy code {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
background: oklch(1 0 0/.08);
padding: 2px 5px;
border-radius: 5px;
}
.ac-18__meta {
margin-top: 4px;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: var(--ac-18-mut);
}
.ac-18__chip {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
line-height: 1.6;
color: var(--ac-18-mut);
padding: 8px 15px;
border: 1px solid oklch(1 0 0/.13);
border-radius: 99px;
text-align: center;
max-width: min(100%,780px);
}
@supports not (background: conic-gradient(from var(--ac-18-a),red,blue)) {
.ac-18__card::before,
.ac-18__bloom {
animation: none;
background: linear-gradient(120deg,var(--ac-18-1),var(--ac-18-2),var(--ac-18-3));
}
}
@media (prefers-reduced-motion: reduce) {
.ac-18__card::before,
.ac-18__bloom {
animation: none;
}
.ac-18__card:hover::before {
animation: none;
}
}
```How this works
CSS cannot animate a gradient directly — but it can animate a registered angle that a gradient reads:
@property --ac-18-a { syntax: '<angle>'; inherits: false; initial-value: 0deg }
.card::before {
content: ''; position: absolute; inset: -2px; z-index: -1;
border-radius: inherit;
background: conic-gradient(from var(--ac-18-a), var(--c1), var(--c2), var(--c3), var(--c1));
animation: ac-18-spin 5s linear infinite;
}
@keyframes ac-18-spin { to { --ac-18-a: 360deg } }Note the repeated first colour at the end of the stop list — without it there is a hard seam where 360° meets 0°.
That version needs an opaque card on top. The mask-composite version cuts a real ring, so the card can be transparent or glassy:
.ring::before {
padding: 2px;
background: conic-gradient(from var(--ac-18-a), …);
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
mask-composite: exclude; /* -webkit-mask-composite: xor */
}Two masks — one clipped to the content box, one to the whole element — subtracted from each other leave exactly the padding ring. It is the cleanest gradient-border technique in CSS today.
The bloom is a second copy of the same gradient at filter: blur(14px) sitting behind everything, so the border appears to cast light onto the page. On hover the spin accelerates from 5 s to 1.6 s, which reads as the card powering up.
Make it yours
- Speed as state: 5 s idle, 1.6 s on hover, 0.8 s while a background job runs — the border becomes a status indicator.
- Partial arc: use a conic gradient with transparent stops (e.g. colour from 0 to 25%, transparent after) for a comet trailing around the edge.
- Two-tone brand ring:
conic-gradient(from var(--a), var(--brand) 0 50%, transparent 50%)gives a hard-edged rotating half-ring. - Reverse on the second card in a pair (
animation-direction: reverse) so a row of cards does not look mechanically synchronised.
Gotchas — read before shipping
- No @property registration = no animation. The angle stays a string and the gradient jumps at 50%. This is the number-one reason the effect "doesn't work".
- Repeat the first colour as the last stop or you get a visible seam at the 0°/360° join.
mask-composite: excludeis the standard; older WebKit needs-webkit-mask-composite: xor. Ship both.- Firefox supported @property only from version 128 (July 2024). Provide a static gradient border fallback in an @supports block for older builds.
- An infinite 5-second spin on twenty cards is twenty live compositor layers. Use it on one or two hero cards, or run it only on hover.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 128+ | 114+ |
Floor set by oklch(), backdrop-filter, @property, text-wrap as this demo is written. The core technique itself goes back further — Chrome 85+.
@property: Chrome 85, Safari 16.4, Firefox 128 — Baseline Newly Available. mask-composite: Chrome 120, Safari 15.4, Firefox 53. An @supports block supplies a static gradient border where @property is missing.