---
name: animated-border-glow
description: Use when you want "Premium, energetic, futuristic" - Animates light around card or button borders.
---

# Animated Border Glow

> **Category:** Premium / Futuristic  -  **Personality:** Premium, energetic, futuristic
>
> **Best use:** CTAs, cards, highlighted content
>
> **Ratings:** Professional 4/5 - Casual 3/5 - Exotic 4/5 - Visual impact 5/5 - Difficulty 3/5 - Performance cost 3/5

## What it is

A bright sliver of light runs continuously around the edge of a card or button, like a charge orbiting a circuit. The border is a `conic-gradient` whose starting angle is animated through a **registered `@property <angle>`** custom property; a CSS `mask` trims that gradient to a thin ring, and a blurred copy underneath throws an outer **bloom** that travels in lockstep. It's pure CSS and GPU-cheap, but it pulls a lot of attention — so reserve it for the *one* thing you want the eye to land on (a primary CTA, the "most popular" plan) on a dark surface where the light can read. Unlike a *static* gradient border, the **motion is the entire point**, which is exactly why you must freeze it to a steady glow under `prefers-reduced-motion`.

## Dependencies / CDN

**None — pure CSS.** It relies on `@property`, `conic-gradient`, and `mask`/`mask-composite` (Chrome 85+, Edge 85+, Safari 16.4+, Firefox 128+). The demo's type is optional Google Fonts; the small JS (billing toggle + pointer sheen) is dressing, not the effect:

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Michroma&family=Sora:wght@400;600;700;800&display=swap" rel="stylesheet">
```

## HTML

Three layers per glowing element: a **bloom** (behind), the **face**/surface (middle, holds content), and the masked **ring** (a pseudo-element, in front).

```html
<article class="abg-card abg-card--hot">
  <span class="abg-bloom" aria-hidden="true"></span>   <!-- outer bloom, sits behind -->
  <div class="abg-face">                                <!-- opaque surface + content -->
    <h3>Pulse</h3>
    <strong>$39<span>/mo</span></strong>
    <!-- features … -->
    <button class="abg-glowbtn">                         <!-- the same effect on a CTA -->
      <span class="abg-glowbtn-bloom" aria-hidden="true"></span>
      <span class="abg-glowbtn-txt">Deploy Pulse &rarr;</span>
    </button>
  </div>
</article>
```

## CSS

```css
/* 1) Register the angle so it can be INTERPOLATED. A plain custom prop is a
      string and would jump between frames — this is the whole trick. */
@property --abg-a{ syntax:"<angle>"; initial-value:0deg; inherits:true }

.abg-card--hot{
  --abg-c1:#22e0ff; --abg-c2:#9b5cff; --abg-c3:#ff3d8b;  /* comet head→tail */
  --abg-bw:1.6px;                                         /* ring thickness  */
  position:relative; isolation:isolate; border-radius:22px;
  animation:abg-orbit 5.2s linear infinite;               /* one anim drives both layers */
}

/* The opaque surface — MUST be its own layer, between bloom (z:0) and ring (z:2) */
.abg-face{
  position:relative; z-index:1; border-radius:inherit; padding:26px;
  background:linear-gradient(180deg,#1e1c34,#0f0e1c);
  border:1px solid rgba(160,150,255,.24);                 /* faint static rim, always visible */
}

/* 2) The sharp ring: a conic comet, masked to JUST the border (sits above the face) */
.abg-card--hot::before{
  content:""; position:absolute; inset:0; z-index:2; border-radius:inherit; pointer-events:none;
  padding:var(--abg-bw);
  background:conic-gradient(from var(--abg-a),
    transparent 90deg, var(--abg-c1) 130deg, var(--abg-c2) 165deg, var(--abg-c3) 200deg, transparent 240deg);
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;                              /* Safari spelling */
          mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite:exclude;                          /* border-box minus content-box = ring */
}

/* 3) The bloom: same comet, blurred, behind the face — bleeds the glow outward */
.abg-bloom{
  position:absolute; inset:-2px; z-index:0; border-radius:inherit; pointer-events:none;
  background:conic-gradient(from var(--abg-a),
    transparent 90deg, var(--abg-c1) 130deg, var(--abg-c2) 165deg, var(--abg-c3) 200deg, transparent 240deg);
  filter:blur(17px) saturate(150%); opacity:.55;
}

/* 4) The only moving part — rotate the angle a full turn */
@keyframes abg-orbit{ to{ --abg-a:360deg } }

/* The CTA reuses the exact pattern at a faster spin */
.abg-glowbtn{ animation:abg-orbit 3.6s linear infinite; /* + ::before ring + .abg-glowbtn-bloom */ }

/* Motion is the point — so freeze it to a FULL static glow when unwelcome */
@media (prefers-reduced-motion:reduce){
  .abg-card--hot, .abg-glowbtn{ animation:none }
  .abg-card--hot::before, .abg-bloom{
    background:conic-gradient(from 210deg,var(--abg-c1),var(--abg-c2),var(--abg-c3),var(--abg-c1));
  }
}
```

## JavaScript

**Not required for the effect** — it is 100% CSS. The demo adds optional, motion-gated dressing (a billing toggle and a cursor-tracked sheen on the card):

```js
var stage=document.querySelector('.abg-stage');
var reduce=matchMedia('(prefers-reduced-motion: reduce)').matches;

// pointer sheen: feed cursor position into the face's radial-gradient
if(!reduce){
  var hot=stage.querySelector('.abg-card--hot');
  hot.addEventListener('pointermove',function(e){
    var r=hot.getBoundingClientRect();
    hot.style.setProperty('--mx',((e.clientX-r.left)/r.width*100)+'%');
    hot.style.setProperty('--my',((e.clientY-r.top)/r.height*100)+'%');
  });
  hot.addEventListener('pointerleave',function(){
    hot.style.setProperty('--mx','50%'); hot.style.setProperty('--my','-12%');
  });
}
```

## How it works

- **`@property --abg-a { syntax:"<angle>" }`** is the linchpin: registering the property tells the engine its type, so the browser can *interpolate* it. Animating an unregistered `--var` would step discretely and the gradient would stutter.
- **`conic-gradient(from var(--abg-a), …)`** paints a "comet" — a short bright arc (cyan→violet→magenta) with the rest transparent. As the angle sweeps `0deg → 360deg`, the bright arc orbits the box.
- **`mask` + `mask-composite:exclude`** keeps only the padding band: one mask layer clipped to `content-box`, one to `border-box`; excluding the inner from the outer leaves a ring exactly `--abg-bw` thick. That turns the full gradient square into a glowing *border*.
- **The bloom** is the identical gradient, un-masked and `blur()`-ed, on a layer *behind* the opaque face. The face hides its center; only the soft halo escapes around the edge, perfectly tracking the sharp ring.
- **One animation, two synced layers:** because `--abg-a` is registered with `inherits:true` and animated on the parent, both the `::before` ring and the `.abg-bloom` child read the same live angle — no second timeline to keep aligned.

## Customization

- **Speed:** the `animation` duration (`5.2s` card, `3.6s` button). Slower = more luxurious; faster = more energetic.
- **Comet length & softness:** the gradient stop angles. A wide bright band (e.g. `60deg → 280deg`) reads as a premium sweep; a narrow one (`120deg → 200deg`) reads as a darting spark.
- **Thickness:** `--abg-bw` (the `::before` padding). The demo bumps it on `:hover` for a "charge-up" feel.
- **Colour:** `--abg-c1/2/3`. One hue = a clean neon ring; three = an iridescent comet.
- **Bloom intensity:** the bloom's `blur()`, `opacity`, and negative `inset` — more of each spreads a bigger halo.
- **Full ring instead of a comet:** drop the `transparent` stops (`conic-gradient(from var(--abg-a), c1, c2, c3, c1)`) for a continuously-lit rotating rainbow border.

## Accessibility & performance

- **Honour `prefers-reduced-motion`** — the motion is the effect, so freeze it to a static full-perimeter glow (shown above) rather than killing the styling.
- Animating a registered custom property repaints the gradient each frame. It's cheap, but **keep it to one or two elements** — glowing every card defeats the purpose and adds paint cost.
- Ensure body text on the `.abg-face` keeps its contrast; the surface stays opaque so the moving light never washes over the copy.
- Decorative layers are `aria-hidden` / `pointer-events:none`, so the ring and bloom never intercept clicks or reach assistive tech.
- Provide a graceful baseline: in a browser without `@property`/`mask` the element simply shows its static `.abg-face` border — still a tidy card.

## Gotchas

- **The `@property` registration is mandatory.** Without it the angle is an un-typed string, can't interpolate, and the gradient snaps frame-to-frame instead of gliding.
- **Mask needs the WebKit twins:** `-webkit-mask` *and* `-webkit-mask-composite:xor` alongside the standard `mask` / `mask-composite:exclude`, or Safari shows a filled square instead of a ring.
- **Don't put `overflow:hidden` on the glowing element** — it clips the bloom. Let the halo bleed; clip at a parent stage instead if you must contain it.
- **Mind the paint order.** The surface must be its own layer *above* the bloom; you can't lean on the element's own `background` with a negative-`z-index` pseudo, because negative-z children paint *above* the element background — the bloom would cover the content. Three explicit layers (bloom `z:0` ▸ face `z:1` ▸ ring `z:2`) is the reliable recipe.
- **Keep a faint static border on the face** so the perimeter still reads where the comet is transparent — otherwise three-quarters of the edge looks unfinished.
