Animated Border
Continuously sweeping gradient border on a card — two stacked CSS pseudo-elements and a background-position animation, zero JavaScript.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Cards has 41 effects, including 6 free. Explore the category →
3 usage examples



How it works
The technique uses a pseudo-element sandwich. The ::before is positioned at inset: -2px — it extends 2 px beyond every side of the card — and receives the gradient on a canvas three times wider than the element (background-size: 300% 300%). The ::after, positioned at inset: 2px, fills the interior with the card's background color (var(--bg-elevated)). Both pseudo-elements are at z-index: -1; since ::after is later in the DOM, it paints on top of ::before and masks the gradient inside. The visible ring between the outer edge of ::before and the outer edge of ::after forms the gradient border.
The @keyframes borderGradient animation slides background-position from 0 50% to 300% 50% over 4 s, linear, infinite — a horizontal sweep of the full canvas width. The gradient linear-gradient(135deg, #6366f1, #d946ef, #06b6d4, #6366f1) repeats the same indigo as first and last stop: the end-to-start transition is imperceptible, making the loop seamlessly continuous.
Corner radii are offset symmetrically: ::before gets border-radius: calc(var(--radius-xl) + 2px) and ::after gets border-radius: calc(var(--radius-xl) - 2px). This ±2 px symmetry ensures inner and outer border curves stay perfectly concentric regardless of the value of --radius-xl.
Accessibility
- prefers-reduced-motion absent: no media query stops the animation. For functional UIs, add manually
@media (prefers-reduced-motion: reduce) { .card-border-gradient::before { animation: none; } }— the border stays visible, static. - CSS pseudo-elements (
::before,::after) are invisible to assistive technologies — no ARIA attributes are needed on these decorative layers. - The host element keeps its native semantics: the effect is purely visual and modifies neither role nor tab order.
- Text contrast depends entirely on the content placed inside the card; the animated border is decorative and does not affect the WCAG ratio.
- No keyboard or pointer interaction in the effect itself — the host element remains focusable according to its native HTML type.
Browser compatibility
Requires CSS animations and the inset shorthand property — supported in all modern browsers since 2021.
Without CSS animations (legacy browsers), the border stays visible but static — the first keyframe (background-position: 0 50%) is displayed. Without inset (IE), pseudo-elements are not positioned and the card shows no border.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="card-border-gradient">
<!-- Your card content -->
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| --bg-elevated | #1a1a24 | Card and inner mask background color (::after) — change both together to keep the border visible. |
| --radius-xl | 1rem | Corner radius. ::before and ::after are recalculated automatically via calc(± 2px). |
| Gradient colors (::before) | #6366f1, #d946ef, #06b6d4 | Stops inside linear-gradient(135deg, …). Repeat the first color as the last stop for a seamless loop. |
| Border width | 2px | Change inset: -2px (::before) and inset: 2px (::after) with the same absolute value. 1px = very thin, 4px = prominent. |
| Animation duration | 4s | Value in the ::before animation property. 2s = snappy, 8s = ambient and subtle. |
| background-size | 300% 300% | Gradient canvas size. At 200% the sweep is shorter and more contrasted; at 400% the scroll is slower. Also adjust the final value in @keyframes. |
| Gradient angle | 135deg | Direction inside linear-gradient. 90deg = pure horizontal sweep, 45deg = rising diagonal. |
FAQ
card-border-gradient to your card and the animation starts automatically. Compatible with any framework (React, Vue, Angular, Svelte) without adaptation.linear-gradient(135deg, …) in the ::before. For a visually continuous loop, repeat the first color as the last stop — for example linear-gradient(135deg, #f59e0b, #ef4444, #8b5cf6, #f59e0b) for a warm palette.prefers-reduced-motion media query. To respect the system preference, add to your CSS: @media (prefers-reduced-motion: reduce) { .card-border-gradient::before { animation: none; } }. The gradient border stays visible, but static.