What would you like to know?
Compare two train faresSummarise a PDFPlan a weekend
Rainbow conic-gradient border that circles the box every 3 s, then every 1 s with a soft glow on hover, focus or tap.
Updated
This effect is free — the Utilities category contains 43 effects total, including 3 free. Effect.Labs has 840 vanilla effects. Explore the category →
Compare two train faresSummarise a PDFPlan a weekend
Channels you follow · 2 live now
Bookshop giveaway · 6 entries
Winner:
Behind the .gsp-box (200 × 120 px, 12 px radius), the ::before pseudo-element sticks out 3 px on every side and fills with a conic-gradient of pink #ff0080, orange #ff8c00, turquoise #40e0d0, violet #7b2ff7, then pink again to close the circle. The ::after pseudo-element, in the background colour, covers the inside 3 px in from the edge: what remains is a 6 px colour band, half outside, half inside.
The gradient starts from an angle, --gsp-angle, declared with @property as a real <angle>, which the gspSpin animation takes from 0 to 360° in 3 s, continuously and at constant speed. On hover, or when something in the box has focus (:focus-within), the duration drops to 1 s and a 4 px blur turns the band into a glow. Changing the duration of a running animation re-times it: as you enter or leave, the colours jump to another angle before turning again.
The label is a link, .gsp-link, whose ::after covers the whole box: a click anywhere follows it, and this link is what makes the effect reachable by keyboard and finger. Replace its href="#gradient-spin" with your URL and its text with yours. The radius is adjustable (--gsp-radius): the band and the inner mask follow it, all the way to a pill or a circle.
--gsp-ring) appears 4 px beyond the band when focus comes from the keyboard. With a field in the box, as in the assistant example, typing is what triggers the speed-up. In Safari, Tab only moves through links if the browser's keyboard setting allows it (Option+Tab otherwise).prefers-reduced-motion: reduce, the rotation stops, the gradient stays visible (pink at the top), and the soft glow still appears on hover and focus.Pure CSS, no JavaScript. Measured in Chromium 151, Firefox 153 and Playwright's WebKit 26.5: rotation, speed-up and glow on hover, Tab, touch, two independent copies, 390 px, and no infinite animation left under reduced motion. Minimum versions from @mdn/browser-compat-data: the rotation needs @property (Chrome 85, Firefox 128, Safari 16.4); the stretched link uses inset (Chrome 87).
Without @property, the angle takes the fallback written in var(--gsp-angle, 0deg): the rainbow border shows, still, and the soft glow keeps working (checked by removing the @property rule in Chromium).
Copy the two blocks (HTML and CSS) into your page. No JavaScript, no dependencies.
<div class="fx-0765-root">
<div class="gsp-box">
<a class="gsp-link" href="#gradient-spin">Gradient Spin</a>
</div>
</div>
.fx-0765-root,
.fx-0765-root *,
.fx-0765-root *::before,
.fx-0765-root *::after {
box-sizing: border-box
}
.fx-0765-root {
--gsp-w: 200px; /* box width */
--gsp-h: 120px; /* box height */
--gsp-radius: 12px; /* box corner radius (the border follows) */
--gsp-width: 3px; /* border thickness */
--gsp-colors: #ff0080, #ff8c00, #40e0d0, #7b2ff7, #ff0080;
--gsp-speed: 3s; /* one turn at rest */
--gsp-speed-hover: 1s; /* one turn on hover, focus, tap */
--gsp-halo: 4px; /* halo blur on hover */
--gsp-fill: #0a0a0f; /* box fill */
--gsp-ring: #fff; /* keyboard focus ring */
display: flex;
align-items: center;
justify-content: center;
width: 100%;
position: relative;
font-family: sans-serif;
}
@property --gsp-angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
.fx-0765-root .gsp-box {
width: var(--gsp-w);
max-width: 100%;
height: var(--gsp-h);
display: flex;
align-items: center;
justify-content: center;
font-size: 0.95rem;
font-weight: 600;
color: white;
border-radius: var(--gsp-radius);
position: relative;
font-family: inherit;
user-select: none;
background: var(--gsp-fill);
z-index: 1;
}
.fx-0765-root .gsp-box::before {
content: '';
position: absolute;
inset: calc(var(--gsp-width) * -1);
border-radius: calc(var(--gsp-radius) + var(--gsp-width));
background: conic-gradient(from var(--gsp-angle, 0deg), var(--gsp-colors));
z-index: -2;
animation: gspSpin var(--gsp-speed) linear infinite;
}
.fx-0765-root .gsp-box::after {
content: '';
position: absolute;
inset: var(--gsp-width);
background: var(--gsp-fill);
border-radius: max(0px, calc(var(--gsp-radius) - var(--gsp-width)));
z-index: -1;
}
.fx-0765-root .gsp-box:hover::before,
.fx-0765-root .gsp-box:focus-within::before {
animation-duration: var(--gsp-speed-hover);
filter: blur(var(--gsp-halo));
}
/* Stretched link: the whole box is clickable, and keyboard focus
or a tap on this link triggers the same state as hover. */
.fx-0765-root .gsp-link {
color: inherit;
text-decoration: none;
outline: none;
}
.fx-0765-root .gsp-link::after {
content: '';
position: absolute;
inset: 0;
border-radius: var(--gsp-radius);
}
.fx-0765-root .gsp-link:focus-visible::after {
outline: 2px solid var(--gsp-ring);
outline-offset: calc(var(--gsp-width) + 4px);
}
@keyframes gspSpin {
to {
--gsp-angle: 360deg;
}
}
@media (prefers-reduced-motion: reduce) {
.fx-0765-root .gsp-box::before,
.fx-0765-root .gsp-box:hover::before,
.fx-0765-root .gsp-box:focus-within::before {
animation: none;
}
}
Pure CSS — no JavaScript needed.
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
--gsp-colors |
#ff0080, #ff8c00, #40e0d0, #7b2ff7, #ff0080 |
List of gradient colours, as many as you like; repeat the first one at the end to avoid a seam. |
--gsp-speed |
3s |
Time for one turn at rest. |
--gsp-speed-hover |
1s |
Time for one turn on hover, focus and tap; use the same value as --gsp-speed to keep only the glow. |
--gsp-halo |
4px |
Blur of the band on hover; 0px removes the glow. |
--gsp-width |
3px |
How far the band reaches on each side of the edge: the visible band is twice that (6 px by default). |
--gsp-radius |
12px |
Box corner radius; band and mask follow. Half the height gives a pill. |
--gsp-w and --gsp-h |
200px and 120px |
Box size; auto lets the content decide, and the width never exceeds the container. |
--gsp-fill |
#0a0a0f |
Background of the box and of the inner mask. |
--gsp-ring |
#fff |
Colour of the keyboard focus ring. |
Hover changes the animation duration (3 s → 1 s) without restarting it: the browser keeps the elapsed time and recomputes where the turn stands, hence a jump in angle. If you prefer a change with no jump, remove animation-duration from the hover rule and keep only the glow (filter).
Put your content in .gsp-box, set --gsp-h: auto so the height follows the content, and give it some padding. If the card leads somewhere, keep a .gsp-link link inside: it makes the whole card clickable and triggers the speed-up from the keyboard.
No: everything hangs off .fx-0765-root and the angle is not inherited, so each box turns on its own. A single root can even hold several .gsp-box elements, like the thumbnail grid in the streaming example.