16 CSS Gradient Animations 05 / 16
CSS Glow on Hover Accent Button
Three button variants — gradient-fill shift, gradient border glow, and flood fill — where the colour palette activates and shifts only on hover, using pseudo-elements and CSS transitions for zero-JS interactivity.
The code
<div class="ga-05">
<div class="ga-05__row">
<span class="ga-05__label">Gradient Fill Shift</span>
<button class="ga-05__btn ga-05__btn--filled">Get Started Free ↗</button>
<button class="ga-05__btn ga-05__btn--filled" style="--c1:#059669;--c2:#0ea5e9;--c3:#7c3aed;">Deploy Now</button>
<button class="ga-05__btn ga-05__btn--filled" style="--c1:#db2777;--c2:#f59e0b;--c3:#ef4444;">Go Premium</button>
</div>
<div class="ga-05__row">
<span class="ga-05__label">Gradient Border Glow</span>
<button class="ga-05__btn ga-05__btn--ghost">Connect Wallet</button>
<button class="ga-05__btn ga-05__btn--ghost" style="--c1:#0ea5e9;--c2:#6366f1;--c3:#a855f7;">View Docs</button>
</div>
<div class="ga-05__row">
<span class="ga-05__label">Flood Fill on Hover</span>
<button class="ga-05__btn ga-05__btn--flood">Subscribe</button>
<button class="ga-05__btn ga-05__btn--flood" style="--c1:#d946ef;--c2:#f43f5e;">Join Waitlist</button>
<button class="ga-05__btn ga-05__btn--flood" style="--c1:#0891b2;--c2:#0d9488;">Learn More</button>
</div>
</div> <div class="ga-05">
<div class="ga-05__row">
<span class="ga-05__label">Gradient Fill Shift</span>
<button class="ga-05__btn ga-05__btn--filled">Get Started Free ↗</button>
<button class="ga-05__btn ga-05__btn--filled" style="--c1:#059669;--c2:#0ea5e9;--c3:#7c3aed;">Deploy Now</button>
<button class="ga-05__btn ga-05__btn--filled" style="--c1:#db2777;--c2:#f59e0b;--c3:#ef4444;">Go Premium</button>
</div>
<div class="ga-05__row">
<span class="ga-05__label">Gradient Border Glow</span>
<button class="ga-05__btn ga-05__btn--ghost">Connect Wallet</button>
<button class="ga-05__btn ga-05__btn--ghost" style="--c1:#0ea5e9;--c2:#6366f1;--c3:#a855f7;">View Docs</button>
</div>
<div class="ga-05__row">
<span class="ga-05__label">Flood Fill on Hover</span>
<button class="ga-05__btn ga-05__btn--flood">Subscribe</button>
<button class="ga-05__btn ga-05__btn--flood" style="--c1:#d946ef;--c2:#f43f5e;">Join Waitlist</button>
<button class="ga-05__btn ga-05__btn--flood" style="--c1:#0891b2;--c2:#0d9488;">Learn More</button>
</div>
</div>.ga-05, .ga-05 *, .ga-05 *::before, .ga-05 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.ga-05 ::selection { background: rgba(139,92,246,.4); color: #fff; }
.ga-05 {
--c1: #7c3aed;
--c2: #06b6d4;
--c3: #ec4899;
--bg: #0d0d18;
--dur: .5s;
width: 100%;
min-height: 100vh;
background: var(--bg);
font-family: system-ui, -apple-system, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 48px;
padding: 48px 24px;
}
/* ── Variant A: gradient background shifts on hover ── */
.ga-05__btn {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 14px 32px;
border-radius: 12px;
border: none;
cursor: pointer;
font-size: 1rem;
font-weight: 700;
letter-spacing: .01em;
overflow: hidden;
transition: transform var(--dur) ease, box-shadow var(--dur) ease;
z-index: 0;
}
/* Shared gradient layer that slides in */
.ga-05__btn::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
background: linear-gradient(135deg, var(--c1), var(--c2), var(--c3));
background-size: 200% 200%;
background-position: 0% 50%;
transition: background-position .6s ease, opacity var(--dur) ease;
z-index: -1;
}
/* Glow halo */
.ga-05__btn::after {
content: '';
position: absolute;
inset: -2px;
border-radius: 14px;
background: linear-gradient(135deg, var(--c1), var(--c2), var(--c3));
background-size: 200% 200%;
background-position: 0% 50%;
filter: blur(16px);
opacity: 0;
transition: opacity var(--dur) ease, background-position .6s ease;
z-index: -2;
}
.ga-05__btn:hover::before { background-position: 100% 50%; }
.ga-05__btn:hover::after { opacity: .65; background-position: 100% 50%; }
.ga-05__btn:hover { transform: translateY(-2px); box-shadow: 0 8px 32px rgba(124,58,237,.3); }
.ga-05__btn:active { transform: translateY(0); }
/* Variant A — filled */
.ga-05__btn--filled {
background: linear-gradient(135deg, var(--c1), var(--c1));
color: #fff;
}
.ga-05__btn--filled::before { opacity: 0; }
.ga-05__btn--filled:hover::before { opacity: 1; }
/* Variant B — ghost with gradient border */
.ga-05__btn--ghost {
background: transparent;
color: #fff;
padding: 13px 31px; /* 1px less to account for border */
}
.ga-05__btn--ghost::before {
/* Gradient border trick: wrapper with gradient bg, inner mask */
background: linear-gradient(var(--bg), var(--bg)) padding-box,
linear-gradient(135deg, rgba(124,58,237,.35), rgba(6,182,212,.35)) border-box;
border: 1px solid transparent;
opacity: 1;
transition: background var(--dur) ease;
}
.ga-05__btn--ghost:hover::before {
background: linear-gradient(var(--bg), var(--bg)) padding-box,
linear-gradient(135deg, var(--c1), var(--c2), var(--c3)) border-box;
}
.ga-05__btn--ghost:hover { box-shadow: 0 0 0 3px rgba(124,58,237,.15); }
/* Variant C — outline that floods with gradient fill */
.ga-05__btn--flood {
background: transparent;
color: #fff;
border: 1.5px solid rgba(255,255,255,.15);
overflow: hidden;
}
.ga-05__btn--flood::before {
opacity: 0;
background: linear-gradient(135deg, var(--c1), var(--c2));
background-size: 100% 100%;
transform: scaleX(0);
transform-origin: left;
transition: transform .4s cubic-bezier(.22,1,.36,1), opacity .1s;
}
.ga-05__btn--flood:hover::before { opacity: 1; transform: scaleX(1); }
.ga-05__btn--flood:hover { border-color: transparent; }
/* Row layout */
.ga-05__row {
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
justify-content: center;
}
.ga-05__label {
font-size: .7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .1em;
color: rgba(255,255,255,.3);
text-align: center;
margin-bottom: -32px;
width: 100%;
}
@media (prefers-reduced-motion: reduce) {
.ga-05__btn, .ga-05__btn::before, .ga-05__btn::after {
transition: none;
animation: none;
}
} .ga-05, .ga-05 *, .ga-05 *::before, .ga-05 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.ga-05 ::selection { background: rgba(139,92,246,.4); color: #fff; }
.ga-05 {
--c1: #7c3aed;
--c2: #06b6d4;
--c3: #ec4899;
--bg: #0d0d18;
--dur: .5s;
width: 100%;
min-height: 100vh;
background: var(--bg);
font-family: system-ui, -apple-system, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 48px;
padding: 48px 24px;
}
/* ── Variant A: gradient background shifts on hover ── */
.ga-05__btn {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 14px 32px;
border-radius: 12px;
border: none;
cursor: pointer;
font-size: 1rem;
font-weight: 700;
letter-spacing: .01em;
overflow: hidden;
transition: transform var(--dur) ease, box-shadow var(--dur) ease;
z-index: 0;
}
/* Shared gradient layer that slides in */
.ga-05__btn::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
background: linear-gradient(135deg, var(--c1), var(--c2), var(--c3));
background-size: 200% 200%;
background-position: 0% 50%;
transition: background-position .6s ease, opacity var(--dur) ease;
z-index: -1;
}
/* Glow halo */
.ga-05__btn::after {
content: '';
position: absolute;
inset: -2px;
border-radius: 14px;
background: linear-gradient(135deg, var(--c1), var(--c2), var(--c3));
background-size: 200% 200%;
background-position: 0% 50%;
filter: blur(16px);
opacity: 0;
transition: opacity var(--dur) ease, background-position .6s ease;
z-index: -2;
}
.ga-05__btn:hover::before { background-position: 100% 50%; }
.ga-05__btn:hover::after { opacity: .65; background-position: 100% 50%; }
.ga-05__btn:hover { transform: translateY(-2px); box-shadow: 0 8px 32px rgba(124,58,237,.3); }
.ga-05__btn:active { transform: translateY(0); }
/* Variant A — filled */
.ga-05__btn--filled {
background: linear-gradient(135deg, var(--c1), var(--c1));
color: #fff;
}
.ga-05__btn--filled::before { opacity: 0; }
.ga-05__btn--filled:hover::before { opacity: 1; }
/* Variant B — ghost with gradient border */
.ga-05__btn--ghost {
background: transparent;
color: #fff;
padding: 13px 31px; /* 1px less to account for border */
}
.ga-05__btn--ghost::before {
/* Gradient border trick: wrapper with gradient bg, inner mask */
background: linear-gradient(var(--bg), var(--bg)) padding-box,
linear-gradient(135deg, rgba(124,58,237,.35), rgba(6,182,212,.35)) border-box;
border: 1px solid transparent;
opacity: 1;
transition: background var(--dur) ease;
}
.ga-05__btn--ghost:hover::before {
background: linear-gradient(var(--bg), var(--bg)) padding-box,
linear-gradient(135deg, var(--c1), var(--c2), var(--c3)) border-box;
}
.ga-05__btn--ghost:hover { box-shadow: 0 0 0 3px rgba(124,58,237,.15); }
/* Variant C — outline that floods with gradient fill */
.ga-05__btn--flood {
background: transparent;
color: #fff;
border: 1.5px solid rgba(255,255,255,.15);
overflow: hidden;
}
.ga-05__btn--flood::before {
opacity: 0;
background: linear-gradient(135deg, var(--c1), var(--c2));
background-size: 100% 100%;
transform: scaleX(0);
transform-origin: left;
transition: transform .4s cubic-bezier(.22,1,.36,1), opacity .1s;
}
.ga-05__btn--flood:hover::before { opacity: 1; transform: scaleX(1); }
.ga-05__btn--flood:hover { border-color: transparent; }
/* Row layout */
.ga-05__row {
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
justify-content: center;
}
.ga-05__label {
font-size: .7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .1em;
color: rgba(255,255,255,.3);
text-align: center;
margin-bottom: -32px;
width: 100%;
}
@media (prefers-reduced-motion: reduce) {
.ga-05__btn, .ga-05__btn::before, .ga-05__btn::after {
transition: none;
animation: none;
}
}How this works
All three variants share a common pattern: the gradient lives on a ::before pseudo-element that is layered below the button text using z-index: -1, and the actual activation is driven by opacity or transform: scaleX() transitions on hover. The glow halo uses a second ::after pseudo-element with the same gradient but with filter: blur(16px) and inset: -2px, creating a soft light spill below the button that transitions from opacity: 0 to opacity: 0.65 on hover. Both pseudo-elements share a background-position transition that walks the gradient from 0% to 100%, making the palette slide rather than simply appear.
The ghost border variant uses the CSS padding-box / border-box background-clip trick: background: linear-gradient(var(--bg), var(--bg)) padding-box, linear-gradient(...) border-box with border: 1px solid transparent. This paints the page background colour inside and the gradient only on the 1px border ring. On hover, the inner linear-gradient(var(--bg), var(--bg)) layer is removed, letting the gradient flood the full button background. The flood-fill variant instead uses transform: scaleX(0) at rest and scaleX(1) on hover with transform-origin: left, sweeping the gradient in from the left edge.
Customize
- Change the gradient palette per button by overriding
--c1,--c2, and--c3as inline style attributes — the entire effect, including the glow, picks up the new colours automatically. - Adjust the glow spread by changing
filter: blur(16px)on.ga-05__btn::after— increase toblur(28px)for a wider, softer ambient halo or decrease toblur(8px)for a tight neon-edge effect. - Tune the hover lift by editing
transform: translateY(-2px)on.ga-05__btn:hover— remove it entirely for a flat feel or increase to-4pxfor a more pronounced pop. - Add a second gradient stop by inserting an extra colour between
--c1and--c2in the gradient declaration and expandingbackground-sizefrom200%to300%. - Replace the flood-fill ease with a spring feel by changing the
cubic-bezier(.22,1,.36,1)tocubic-bezier(.34,1.56,.64,1)— this adds a small overshoot that reads as elastic.
Watch out for
- The ghost border trick (
padding-box / border-boxbackground-clip) requiresborder: Npx solid transparentto be set — if the border isnonethe gradient border disappears entirely because there is no border area to paint. - Setting
overflow: hiddenon the button clips the::afterglow halo — useoverflow: visibleand instead apply overflow clipping to a wrapper if you need it, or usebox-shadowas a non-clipping alternative. z-index: -1on::beforeplaces it behind the text but also behind the button background if the parent hasbackground: transparentand a non-transparent stacking parent — always set an explicitbackgroundon the button itself.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 49+ | 9.1+ | 36+ | 49+ |
The padding-box / border-box background-clip border trick requires Chrome 1+, Safari 3+, Firefox 3.5+ — universally supported. All transitions are on compositable properties.