18 CSS Gradient UI01 / 18

Pure CSSMIT licensed

CSS Gradient Button with Hover Animation

Six production-ready gradient CTA buttons on one screen — background-position shift, shine sweep, spinning conic ring, glow lift, ghost fill and an oklch hue-drift — every hover animates on the compositor, no JavaScript, no repaint jank.

Published

Live Demo
Try it

The code

<div class="cgu-01-group">
<section class="cgu-01" aria-label="CSS gradient button hover animation demos">
  <div class="cgu-01__inner">
    <header class="cgu-01__head">
      <p class="cgu-01__eyebrow">01 · Gradient buttons</p>
      <h2 class="cgu-01__title">Hover every button.<br>Zero JavaScript.</h2>
      <p class="cgu-01__sub">Six compositor-only gradient hover animations — shift, sweep, spin, glow, fill and hue-drift.</p>
    </header>
    <div class="cgu-01__grid">
      <div class="cgu-01__cell"><button class="cgu-01__btn cgu-01__btn--shift" type="button">Get started free</button><p class="cgu-01__cap">Position shift · <code>background-position</code></p></div>
      <div class="cgu-01__cell"><button class="cgu-01__btn cgu-01__btn--sweep" type="button">Start building</button><p class="cgu-01__cap">Shine sweep · skewed <code>::after</code></p></div>
      <div class="cgu-01__cell"><button class="cgu-01__btn cgu-01__btn--ring" type="button">Upgrade to Pro</button><p class="cgu-01__cap">Conic ring spin · <code>@property</code> angle</p></div>
      <div class="cgu-01__cell"><button class="cgu-01__btn cgu-01__btn--glow" type="button">Deploy now</button><p class="cgu-01__cap">Glow lift · blurred <code>::before</code> twin</p></div>
      <div class="cgu-01__cell"><button class="cgu-01__btn cgu-01__btn--ghost" type="button">View pricing</button><p class="cgu-01__cap">Ghost fill · gradient border → solid</p></div>
      <div class="cgu-01__cell"><button class="cgu-01__btn cgu-01__btn--hue" type="button">Join waitlist</button><p class="cgu-01__cap">Hue drift · animated <code>oklch()</code> hue</p></div>
    </div>
    <p class="cgu-01__foot">Tab through the buttons — every effect also fires on <code>:focus-visible</code>.</p>
  </div>
</section>
</div>
.cgu-01-group {
  display: block;
  width: 100%;
}

.cgu-01,
.cgu-01 *,
.cgu-01 *::before,
.cgu-01 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

@property --cgu01-a {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

@property --cgu01-h {
  syntax: '<number>';
  initial-value: 290;
  inherits: false;
}

.cgu-01 {
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 64px 24px;
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: #eef0ff;
  background: radial-gradient(120% 90% at 50% -10%,#1b1440 0%,#0b0d16 55%,#07080e 100%);
}

.cgu-01__inner {
  width: min(1060px,100%);
  display: grid;
  gap: 44px;
}

.cgu-01__head {
  text-align: center;
  display: grid;
  gap: 14px;
  justify-items: center;
}

.cgu-01__eyebrow {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: #8f7bff;
}

.cgu-01__title {
  font-size: clamp(30px,4.6vw,54px);
  line-height: 1.06;
  font-weight: 800;
  letter-spacing: -.02em;
  text-wrap: balance;
}

.cgu-01__sub {
  max-width: 52ch;
  font-size: 15.5px;
  line-height: 1.6;
  color: #a7abc8;
  text-wrap: pretty;
}

.cgu-01__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit,minmax(280px,1fr));
  gap: 22px;
}

.cgu-01__cell {
  display: grid;
  gap: 14px;
  justify-items: center;
  align-content: center;
  padding: 38px 22px;
  border-radius: 20px;
  background: rgba(255,255,255,.03);
  border: 1px solid rgba(255,255,255,.07);
}

.cgu-01__cap {
  font-size: 12.5px;
  color: #8b90b0;
}

.cgu-01__cap code {
  font-family: ui-monospace,Menlo,monospace;
  font-size: 11.5px;
  color: #b9beff;
  background: rgba(139,123,255,.12);
  padding: 1px 5px;
  border-radius: 5px;
}

.cgu-01__btn {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  font: inherit;
  font-size: 15.5px;
  font-weight: 700;
  color: #fff;
  padding: 15px 34px;
  border: none;
  border-radius: 9999px;
  cursor: pointer;
}

.cgu-01__btn:focus-visible {
  outline: 3px solid #cdd2ff;
  outline-offset: 3px;
}

.cgu-01__btn--shift {
  background: linear-gradient(120deg,#7c3aed,#ec4899,#f97316);
  background-size: 220% 100%;
  background-position: 0 0;
  transition: background-position .5s ease,box-shadow .3s;
  box-shadow: 0 14px 30px -14px rgba(236,72,153,.55);
}

.cgu-01__btn--shift:hover,
.cgu-01__btn--shift:focus-visible {
  background-position: 100% 0;
  box-shadow: 0 18px 36px -14px rgba(249,115,22,.6);
}

.cgu-01__btn--sweep {
  background: linear-gradient(135deg,oklch(0.55 0.22 265),oklch(0.6 0.24 305));
}

.cgu-01__btn--sweep::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 -70%;
  width: 45%;
  background: linear-gradient(105deg,transparent,rgba(255,255,255,.55),transparent);
  transform: skewX(-18deg);
  transition: left .55s ease;
}

.cgu-01__btn--sweep:hover::after,
.cgu-01__btn--sweep:focus-visible::after {
  left: 125%;
}

.cgu-01__btn--ring {
  background: #12142a;
  border-radius: 14px;
}

.cgu-01__btn--ring::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 2px;
  border-radius: inherit;
  background: conic-gradient(from var(--cgu01-a),#22d3ee,#818cf8,#e879f9,#22d3ee);
  -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;
  z-index: -1;
}

.cgu-01__btn--ring:hover::before,
.cgu-01__btn--ring:focus-visible::before {
  animation: cgu01-spin 1.6s linear infinite;
}

.cgu-01__btn--glow {
  background: linear-gradient(120deg,#06b6d4,#3b82f6);
  transition: transform .3s cubic-bezier(.2,.9,.3,1.4);
}

.cgu-01__btn--glow::before {
  content: "";
  position: absolute;
  inset: -6px;
  border-radius: inherit;
  background: inherit;
  filter: blur(18px);
  opacity: 0;
  transition: opacity .35s;
  z-index: -1;
}

.cgu-01__btn--glow:hover,
.cgu-01__btn--glow:focus-visible {
  transform: translateY(-3px);
}

.cgu-01__btn--glow:hover::before,
.cgu-01__btn--glow:focus-visible::before {
  opacity: .85;
}

.cgu-01__btn--ghost {
  background: transparent;
  border-radius: 14px;
  color: #e9d5ff;
  transition: color .35s;
}

.cgu-01__btn--ghost::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 2px;
  border-radius: inherit;
  background: linear-gradient(120deg,#a855f7,#ec4899);
  -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;
}

.cgu-01__btn--ghost::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(120deg,#a855f7,#ec4899);
  opacity: 0;
  transition: opacity .35s;
  z-index: -1;
}

.cgu-01__btn--ghost:hover,
.cgu-01__btn--ghost:focus-visible {
  color: #fff;
}

.cgu-01__btn--ghost:hover::after,
.cgu-01__btn--ghost:focus-visible::after {
  opacity: 1;
}

.cgu-01__btn--hue {
  background: linear-gradient(130deg,oklch(0.55 0.24 calc(var(--cgu01-h) * 1deg)),oklch(0.68 0.2 calc((var(--cgu01-h) + 60) * 1deg)));
}

.cgu-01__btn--hue:hover,
.cgu-01__btn--hue:focus-visible {
  animation: cgu01-hue 2.4s linear infinite;
}

.cgu-01__foot {
  text-align: center;
  font-size: 13px;
  color: #7c81a3;
}

.cgu-01__foot code {
  font-family: ui-monospace,Menlo,monospace;
  font-size: 12px;
  color: #b9beff;
}

@keyframes cgu01-spin {
  to {
    --cgu01-a: 360deg;
  }
}

@keyframes cgu01-hue {
  to {
    --cgu01-h: 650;
  }
}

@media (prefers-reduced-motion: reduce) {
  .cgu-01 * {
    transition-duration: .01s!important;
    animation: none!important;
  }

  .cgu-01__btn--glow:hover::before {
    opacity: .85;
  }
}
/* No JavaScript — all six hover animations run on background-position, transform, opacity and @property-registered angle/hue custom properties. */
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Gradient UI 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: CSS Gradient Button with Hover Animation
Source: https://codefronts.com/design-styles/css-gradient-ui/css-gradient-button-with-hover-animation/

Six production-ready gradient CTA buttons on one screen — background-position shift, shine sweep, spinning conic ring, glow lift, ghost fill and an oklch hue-drift — every hover animates on the compositor, no JavaScript, no repaint jank.
## HTML
```html
<div class="cgu-01-group">
<section class="cgu-01" aria-label="CSS gradient button hover animation demos">
  <div class="cgu-01__inner">
    <header class="cgu-01__head">
      <p class="cgu-01__eyebrow">01 · Gradient buttons</p>
      <h2 class="cgu-01__title">Hover every button.<br>Zero JavaScript.</h2>
      <p class="cgu-01__sub">Six compositor-only gradient hover animations — shift, sweep, spin, glow, fill and hue-drift.</p>
    </header>
    <div class="cgu-01__grid">
      <div class="cgu-01__cell"><button class="cgu-01__btn cgu-01__btn--shift" type="button">Get started free</button><p class="cgu-01__cap">Position shift · <code>background-position</code></p></div>
      <div class="cgu-01__cell"><button class="cgu-01__btn cgu-01__btn--sweep" type="button">Start building</button><p class="cgu-01__cap">Shine sweep · skewed <code>::after</code></p></div>
      <div class="cgu-01__cell"><button class="cgu-01__btn cgu-01__btn--ring" type="button">Upgrade to Pro</button><p class="cgu-01__cap">Conic ring spin · <code>@property</code> angle</p></div>
      <div class="cgu-01__cell"><button class="cgu-01__btn cgu-01__btn--glow" type="button">Deploy now</button><p class="cgu-01__cap">Glow lift · blurred <code>::before</code> twin</p></div>
      <div class="cgu-01__cell"><button class="cgu-01__btn cgu-01__btn--ghost" type="button">View pricing</button><p class="cgu-01__cap">Ghost fill · gradient border → solid</p></div>
      <div class="cgu-01__cell"><button class="cgu-01__btn cgu-01__btn--hue" type="button">Join waitlist</button><p class="cgu-01__cap">Hue drift · animated <code>oklch()</code> hue</p></div>
    </div>
    <p class="cgu-01__foot">Tab through the buttons — every effect also fires on <code>:focus-visible</code>.</p>
  </div>
</section>
</div>
```
## CSS
```css
.cgu-01-group {
  display: block;
  width: 100%;
}

.cgu-01,
.cgu-01 *,
.cgu-01 *::before,
.cgu-01 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

@property --cgu01-a {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

@property --cgu01-h {
  syntax: '<number>';
  initial-value: 290;
  inherits: false;
}

.cgu-01 {
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 64px 24px;
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: #eef0ff;
  background: radial-gradient(120% 90% at 50% -10%,#1b1440 0%,#0b0d16 55%,#07080e 100%);
}

.cgu-01__inner {
  width: min(1060px,100%);
  display: grid;
  gap: 44px;
}

.cgu-01__head {
  text-align: center;
  display: grid;
  gap: 14px;
  justify-items: center;
}

.cgu-01__eyebrow {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: #8f7bff;
}

.cgu-01__title {
  font-size: clamp(30px,4.6vw,54px);
  line-height: 1.06;
  font-weight: 800;
  letter-spacing: -.02em;
  text-wrap: balance;
}

.cgu-01__sub {
  max-width: 52ch;
  font-size: 15.5px;
  line-height: 1.6;
  color: #a7abc8;
  text-wrap: pretty;
}

.cgu-01__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit,minmax(280px,1fr));
  gap: 22px;
}

.cgu-01__cell {
  display: grid;
  gap: 14px;
  justify-items: center;
  align-content: center;
  padding: 38px 22px;
  border-radius: 20px;
  background: rgba(255,255,255,.03);
  border: 1px solid rgba(255,255,255,.07);
}

.cgu-01__cap {
  font-size: 12.5px;
  color: #8b90b0;
}

.cgu-01__cap code {
  font-family: ui-monospace,Menlo,monospace;
  font-size: 11.5px;
  color: #b9beff;
  background: rgba(139,123,255,.12);
  padding: 1px 5px;
  border-radius: 5px;
}

.cgu-01__btn {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  font: inherit;
  font-size: 15.5px;
  font-weight: 700;
  color: #fff;
  padding: 15px 34px;
  border: none;
  border-radius: 9999px;
  cursor: pointer;
}

.cgu-01__btn:focus-visible {
  outline: 3px solid #cdd2ff;
  outline-offset: 3px;
}

.cgu-01__btn--shift {
  background: linear-gradient(120deg,#7c3aed,#ec4899,#f97316);
  background-size: 220% 100%;
  background-position: 0 0;
  transition: background-position .5s ease,box-shadow .3s;
  box-shadow: 0 14px 30px -14px rgba(236,72,153,.55);
}

.cgu-01__btn--shift:hover,
.cgu-01__btn--shift:focus-visible {
  background-position: 100% 0;
  box-shadow: 0 18px 36px -14px rgba(249,115,22,.6);
}

.cgu-01__btn--sweep {
  background: linear-gradient(135deg,oklch(0.55 0.22 265),oklch(0.6 0.24 305));
}

.cgu-01__btn--sweep::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 -70%;
  width: 45%;
  background: linear-gradient(105deg,transparent,rgba(255,255,255,.55),transparent);
  transform: skewX(-18deg);
  transition: left .55s ease;
}

.cgu-01__btn--sweep:hover::after,
.cgu-01__btn--sweep:focus-visible::after {
  left: 125%;
}

.cgu-01__btn--ring {
  background: #12142a;
  border-radius: 14px;
}

.cgu-01__btn--ring::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 2px;
  border-radius: inherit;
  background: conic-gradient(from var(--cgu01-a),#22d3ee,#818cf8,#e879f9,#22d3ee);
  -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;
  z-index: -1;
}

.cgu-01__btn--ring:hover::before,
.cgu-01__btn--ring:focus-visible::before {
  animation: cgu01-spin 1.6s linear infinite;
}

.cgu-01__btn--glow {
  background: linear-gradient(120deg,#06b6d4,#3b82f6);
  transition: transform .3s cubic-bezier(.2,.9,.3,1.4);
}

.cgu-01__btn--glow::before {
  content: "";
  position: absolute;
  inset: -6px;
  border-radius: inherit;
  background: inherit;
  filter: blur(18px);
  opacity: 0;
  transition: opacity .35s;
  z-index: -1;
}

.cgu-01__btn--glow:hover,
.cgu-01__btn--glow:focus-visible {
  transform: translateY(-3px);
}

.cgu-01__btn--glow:hover::before,
.cgu-01__btn--glow:focus-visible::before {
  opacity: .85;
}

.cgu-01__btn--ghost {
  background: transparent;
  border-radius: 14px;
  color: #e9d5ff;
  transition: color .35s;
}

.cgu-01__btn--ghost::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 2px;
  border-radius: inherit;
  background: linear-gradient(120deg,#a855f7,#ec4899);
  -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;
}

.cgu-01__btn--ghost::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(120deg,#a855f7,#ec4899);
  opacity: 0;
  transition: opacity .35s;
  z-index: -1;
}

.cgu-01__btn--ghost:hover,
.cgu-01__btn--ghost:focus-visible {
  color: #fff;
}

.cgu-01__btn--ghost:hover::after,
.cgu-01__btn--ghost:focus-visible::after {
  opacity: 1;
}

.cgu-01__btn--hue {
  background: linear-gradient(130deg,oklch(0.55 0.24 calc(var(--cgu01-h) * 1deg)),oklch(0.68 0.2 calc((var(--cgu01-h) + 60) * 1deg)));
}

.cgu-01__btn--hue:hover,
.cgu-01__btn--hue:focus-visible {
  animation: cgu01-hue 2.4s linear infinite;
}

.cgu-01__foot {
  text-align: center;
  font-size: 13px;
  color: #7c81a3;
}

.cgu-01__foot code {
  font-family: ui-monospace,Menlo,monospace;
  font-size: 12px;
  color: #b9beff;
}

@keyframes cgu01-spin {
  to {
    --cgu01-a: 360deg;
  }
}

@keyframes cgu01-hue {
  to {
    --cgu01-h: 650;
  }
}

@media (prefers-reduced-motion: reduce) {
  .cgu-01 * {
    transition-duration: .01s!important;
    animation: none!important;
  }

  .cgu-01__btn--glow:hover::before {
    opacity: .85;
  }
}
```

## JavaScript
```js
/* No JavaScript — all six hover animations run on background-position, transform, opacity and @property-registered angle/hue custom properties. */
```

How this works

The core trick behind smooth gradient hovers: you cannot transition background-image, so each button animates something that moves the gradient instead. The workhorse is an oversized gradient shifted with background-position:

background: linear-gradient(120deg, #7c3aed, #ec4899, #f97316);
background-size: 220% 100%;
transition: background-position .5s;
/* on :hover */ background-position: 100% 0;

The spinning ring registers an angle with @property --cgu01-a { syntax: '<angle>' } so a conic-gradient(from var(--cgu01-a), …) border can be keyframe-animated — impossible with unregistered custom properties. The glow variant duplicates the gradient in a blurred ::before and only animates its opacity + a translateY lift, keeping everything on the GPU.

Make it yours

  • Swap palettes by editing the three stops in each button's gradient — oklch stops keep hue transitions clean.
  • Slow the shine sweep via the ::after transition duration.
  • Retarget the conic ring speed with the cgu01-spin keyframe duration.
  • Make any button a link: swap <button> for <a role="button"> — all styles are class-based.

Gotchas — read before shipping

  • Never transition background-image itself — it snaps. Move background-position over an oversized gradient instead.
  • Register angles with @property or the conic spin will jump instead of rotate.
  • Keep text ≥ 4.5:1 contrast against the lightest gradient stop, not the average.
  • Add isolation: isolate so glow pseudo-elements never leak behind neighbouring cards.

Browser support

ChromeSafariFirefoxEdge
114+17.5+128+114+

Floor set by oklch(), @property, text-wrap as this demo is written. The core technique itself goes back further — Chrome 85+.

@property is Baseline 2024; without it the ring button falls back to a static gradient border — still fully usable. Everything else works back to 2020 evergreens.

Techniques used in this demo

Search CodeFronts

Loading…