51 CSS Buttons05 / 51
CSS Gradient CTA Button
A conversion-grade CTA with a conic gradient ring that rotates continuously, a soft bloom underneath, and a mesh gradient body that shifts on hover. The rotation is possible at all because @property registers the angle as an <angle> type — plain custom properties cannot be animated.
Published
The code
<div class="cb-05">
<div class="cb-05__stage">
<button class="cb-05__btn" type="button">
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M13 3 4.5 13.5H11l-1 7.5 8.5-10.5H12l1-7.5z"/></svg>
<span>Upgrade to Pro</span>
</button>
</div>
</div><div class="cb-05">
<div class="cb-05__stage">
<button class="cb-05__btn" type="button">
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M13 3 4.5 13.5H11l-1 7.5 8.5-10.5H12l1-7.5z"/></svg>
<span>Upgrade to Pro</span>
</button>
</div>
</div>@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700&display=swap');
@property --cb-05-a {
syntax: '<angle>';
inherits: false;
initial-value: 135deg;
}
.cb-05 {
--bg: #08070d;
--c1: #6d28d9;
--c2: #2dd4bf;
--c3: #f472b6;
--ink: #ffffff;
--body: #141024;
--bloom: .55;
--radius: 1rem;
--sans: "Sora",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: radial-gradient(60rem 40rem at 50% 120%, color-mix(in oklab,var(--c1) 22%,transparent), transparent 70%), var(--bg);
font-family: var(--sans);
-webkit-font-smoothing: antialiased;
}
@supports (color: oklch(50% 0 0)) {
.cb-05 {
--c1: oklch(52% .24 293);
--c2: oklch(80% .14 182);
--c3: oklch(74% .17 349);
--body: oklch(20% .04 290);
}
}
@media (prefers-color-scheme: light) {
.cb-05:not([data-theme="dark"]) {
--bg: #f5f4fb;
--body: #1a1530;
--bloom: .4;
}
}
.cb-05[data-theme="light"] {
--bg: #f5f4fb;
--body: #1a1530;
--bloom: .4;
}
.cb-05,
.cb-05 *,
.cb-05 *::before,
.cb-05 *::after {
box-sizing: border-box;
}
.cb-05__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.5rem,6vw,4rem);
}
.cb-05__btn {
position: relative;
isolation: isolate;
cursor: pointer;
display: grid;
grid-auto-flow: column;
align-items: center;
gap: .55rem;
min-height: 3.5rem;
padding: 0 1.9rem;
border: 0;
border-radius: var(--radius);
font: inherit;
font-size: 1rem;
font-weight: 600;
letter-spacing: -.005em;
color: var(--ink);
background: radial-gradient(120% 140% at 15% 0%, color-mix(in oklab,var(--c1) 55%,transparent), transparent 60%), radial-gradient(120% 140% at 90% 100%, color-mix(in oklab,var(--c2) 35%,transparent), transparent 60%), var(--body);
background-size: 150% 150%,150% 150%,100% 100%;
background-position: 0% 0%,100% 100%,0 0;
transition: translate .3s cubic-bezier(.16,1,.3,1), background-position .6s cubic-bezier(.16,1,.3,1), scale .18s ease;
}
.cb-05__btn svg {
width: 1.1rem;
height: 1.1rem;
color: var(--c2);
}
.cb-05__btn::before,
.cb-05__btn::after {
content: '';
position: absolute;
inset: -2px;
z-index: -1;
border-radius: inherit;
background: conic-gradient(from var(--cb-05-a),var(--c1),var(--c2),var(--c3),var(--c1));
animation: cb-05-spin 4.5s linear infinite;
}
.cb-05__btn::after {
inset: 2px;
filter: blur(20px);
opacity: var(--bloom);
transition: opacity .35s ease;
}
@keyframes cb-05-spin {
to {
--cb-05-a: 495deg;
}
}
.cb-05__btn:hover {
translate: 0 -2px;
background-position: 30% 20%,70% 80%,0 0;
}
.cb-05__btn:hover::after {
opacity: calc(var(--bloom) + .35);
}
.cb-05__btn:active {
scale: .98;
translate: 0 0;
}
.cb-05__btn:focus-visible {
outline: 2px solid var(--c2);
outline-offset: 5px;
}
.cb-05__btn:disabled {
cursor: not-allowed;
filter: grayscale(.85);
opacity: .55;
}
.cb-05__btn:disabled::before,
.cb-05__btn:disabled::after {
animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) {
.cb-05__btn::before,
.cb-05__btn::after {
animation: none;
}
.cb-05__btn {
transition: none;
}
}
@media (forced-colors: active) {
.cb-05__btn {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
}
.cb-05__btn::before,
.cb-05__btn::after {
display: none;
}
}@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700&display=swap');
@property --cb-05-a {
syntax: '<angle>';
inherits: false;
initial-value: 135deg;
}
.cb-05 {
--bg: #08070d;
--c1: #6d28d9;
--c2: #2dd4bf;
--c3: #f472b6;
--ink: #ffffff;
--body: #141024;
--bloom: .55;
--radius: 1rem;
--sans: "Sora",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: radial-gradient(60rem 40rem at 50% 120%, color-mix(in oklab,var(--c1) 22%,transparent), transparent 70%), var(--bg);
font-family: var(--sans);
-webkit-font-smoothing: antialiased;
}
@supports (color: oklch(50% 0 0)) {
.cb-05 {
--c1: oklch(52% .24 293);
--c2: oklch(80% .14 182);
--c3: oklch(74% .17 349);
--body: oklch(20% .04 290);
}
}
@media (prefers-color-scheme: light) {
.cb-05:not([data-theme="dark"]) {
--bg: #f5f4fb;
--body: #1a1530;
--bloom: .4;
}
}
.cb-05[data-theme="light"] {
--bg: #f5f4fb;
--body: #1a1530;
--bloom: .4;
}
.cb-05,
.cb-05 *,
.cb-05 *::before,
.cb-05 *::after {
box-sizing: border-box;
}
.cb-05__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.5rem,6vw,4rem);
}
.cb-05__btn {
position: relative;
isolation: isolate;
cursor: pointer;
display: grid;
grid-auto-flow: column;
align-items: center;
gap: .55rem;
min-height: 3.5rem;
padding: 0 1.9rem;
border: 0;
border-radius: var(--radius);
font: inherit;
font-size: 1rem;
font-weight: 600;
letter-spacing: -.005em;
color: var(--ink);
background: radial-gradient(120% 140% at 15% 0%, color-mix(in oklab,var(--c1) 55%,transparent), transparent 60%), radial-gradient(120% 140% at 90% 100%, color-mix(in oklab,var(--c2) 35%,transparent), transparent 60%), var(--body);
background-size: 150% 150%,150% 150%,100% 100%;
background-position: 0% 0%,100% 100%,0 0;
transition: translate .3s cubic-bezier(.16,1,.3,1), background-position .6s cubic-bezier(.16,1,.3,1), scale .18s ease;
}
.cb-05__btn svg {
width: 1.1rem;
height: 1.1rem;
color: var(--c2);
}
.cb-05__btn::before,
.cb-05__btn::after {
content: '';
position: absolute;
inset: -2px;
z-index: -1;
border-radius: inherit;
background: conic-gradient(from var(--cb-05-a),var(--c1),var(--c2),var(--c3),var(--c1));
animation: cb-05-spin 4.5s linear infinite;
}
.cb-05__btn::after {
inset: 2px;
filter: blur(20px);
opacity: var(--bloom);
transition: opacity .35s ease;
}
@keyframes cb-05-spin {
to {
--cb-05-a: 495deg;
}
}
.cb-05__btn:hover {
translate: 0 -2px;
background-position: 30% 20%,70% 80%,0 0;
}
.cb-05__btn:hover::after {
opacity: calc(var(--bloom) + .35);
}
.cb-05__btn:active {
scale: .98;
translate: 0 0;
}
.cb-05__btn:focus-visible {
outline: 2px solid var(--c2);
outline-offset: 5px;
}
.cb-05__btn:disabled {
cursor: not-allowed;
filter: grayscale(.85);
opacity: .55;
}
.cb-05__btn:disabled::before,
.cb-05__btn:disabled::after {
animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) {
.cb-05__btn::before,
.cb-05__btn::after {
animation: none;
}
.cb-05__btn {
transition: none;
}
}
@media (forced-colors: active) {
.cb-05__btn {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
}
.cb-05__btn::before,
.cb-05__btn::after {
display: none;
}
}Here's a working CSS Button 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 CTA Button
Source: https://codefronts.com/components/css-buttons/css-gradient-cta-button/
A conversion-grade CTA with a conic gradient ring that rotates continuously, a soft bloom underneath, and a mesh gradient body that shifts on hover. The rotation is possible at all because @property registers the angle as an <angle> type — plain custom properties cannot be animated.
## HTML
```html
<div class="cb-05">
<div class="cb-05__stage">
<button class="cb-05__btn" type="button">
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M13 3 4.5 13.5H11l-1 7.5 8.5-10.5H12l1-7.5z"/></svg>
<span>Upgrade to Pro</span>
</button>
</div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700&display=swap');
@property --cb-05-a {
syntax: '<angle>';
inherits: false;
initial-value: 135deg;
}
.cb-05 {
--bg: #08070d;
--c1: #6d28d9;
--c2: #2dd4bf;
--c3: #f472b6;
--ink: #ffffff;
--body: #141024;
--bloom: .55;
--radius: 1rem;
--sans: "Sora",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: radial-gradient(60rem 40rem at 50% 120%, color-mix(in oklab,var(--c1) 22%,transparent), transparent 70%), var(--bg);
font-family: var(--sans);
-webkit-font-smoothing: antialiased;
}
@supports (color: oklch(50% 0 0)) {
.cb-05 {
--c1: oklch(52% .24 293);
--c2: oklch(80% .14 182);
--c3: oklch(74% .17 349);
--body: oklch(20% .04 290);
}
}
@media (prefers-color-scheme: light) {
.cb-05:not([data-theme="dark"]) {
--bg: #f5f4fb;
--body: #1a1530;
--bloom: .4;
}
}
.cb-05[data-theme="light"] {
--bg: #f5f4fb;
--body: #1a1530;
--bloom: .4;
}
.cb-05,
.cb-05 *,
.cb-05 *::before,
.cb-05 *::after {
box-sizing: border-box;
}
.cb-05__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.5rem,6vw,4rem);
}
.cb-05__btn {
position: relative;
isolation: isolate;
cursor: pointer;
display: grid;
grid-auto-flow: column;
align-items: center;
gap: .55rem;
min-height: 3.5rem;
padding: 0 1.9rem;
border: 0;
border-radius: var(--radius);
font: inherit;
font-size: 1rem;
font-weight: 600;
letter-spacing: -.005em;
color: var(--ink);
background: radial-gradient(120% 140% at 15% 0%, color-mix(in oklab,var(--c1) 55%,transparent), transparent 60%), radial-gradient(120% 140% at 90% 100%, color-mix(in oklab,var(--c2) 35%,transparent), transparent 60%), var(--body);
background-size: 150% 150%,150% 150%,100% 100%;
background-position: 0% 0%,100% 100%,0 0;
transition: translate .3s cubic-bezier(.16,1,.3,1), background-position .6s cubic-bezier(.16,1,.3,1), scale .18s ease;
}
.cb-05__btn svg {
width: 1.1rem;
height: 1.1rem;
color: var(--c2);
}
.cb-05__btn::before,
.cb-05__btn::after {
content: '';
position: absolute;
inset: -2px;
z-index: -1;
border-radius: inherit;
background: conic-gradient(from var(--cb-05-a),var(--c1),var(--c2),var(--c3),var(--c1));
animation: cb-05-spin 4.5s linear infinite;
}
.cb-05__btn::after {
inset: 2px;
filter: blur(20px);
opacity: var(--bloom);
transition: opacity .35s ease;
}
@keyframes cb-05-spin {
to {
--cb-05-a: 495deg;
}
}
.cb-05__btn:hover {
translate: 0 -2px;
background-position: 30% 20%,70% 80%,0 0;
}
.cb-05__btn:hover::after {
opacity: calc(var(--bloom) + .35);
}
.cb-05__btn:active {
scale: .98;
translate: 0 0;
}
.cb-05__btn:focus-visible {
outline: 2px solid var(--c2);
outline-offset: 5px;
}
.cb-05__btn:disabled {
cursor: not-allowed;
filter: grayscale(.85);
opacity: .55;
}
.cb-05__btn:disabled::before,
.cb-05__btn:disabled::after {
animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) {
.cb-05__btn::before,
.cb-05__btn::after {
animation: none;
}
.cb-05__btn {
transition: none;
}
}
@media (forced-colors: active) {
.cb-05__btn {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
}
.cb-05__btn::before,
.cb-05__btn::after {
display: none;
}
}
```Here's a working CSS Button 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 CTA Button
Source: https://codefronts.com/components/css-buttons/css-gradient-cta-button/
A conversion-grade CTA with a conic gradient ring that rotates continuously, a soft bloom underneath, and a mesh gradient body that shifts on hover. The rotation is possible at all because @property registers the angle as an <angle> type — plain custom properties cannot be animated.
## HTML
```html
<div class="cb-05">
<div class="cb-05__stage">
<button class="cb-05__btn" type="button">
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M13 3 4.5 13.5H11l-1 7.5 8.5-10.5H12l1-7.5z"/></svg>
<span>Upgrade to Pro</span>
</button>
</div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700&display=swap');
@property --cb-05-a {
syntax: '<angle>';
inherits: false;
initial-value: 135deg;
}
.cb-05 {
--bg: #08070d;
--c1: #6d28d9;
--c2: #2dd4bf;
--c3: #f472b6;
--ink: #ffffff;
--body: #141024;
--bloom: .55;
--radius: 1rem;
--sans: "Sora",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: radial-gradient(60rem 40rem at 50% 120%, color-mix(in oklab,var(--c1) 22%,transparent), transparent 70%), var(--bg);
font-family: var(--sans);
-webkit-font-smoothing: antialiased;
}
@supports (color: oklch(50% 0 0)) {
.cb-05 {
--c1: oklch(52% .24 293);
--c2: oklch(80% .14 182);
--c3: oklch(74% .17 349);
--body: oklch(20% .04 290);
}
}
@media (prefers-color-scheme: light) {
.cb-05:not([data-theme="dark"]) {
--bg: #f5f4fb;
--body: #1a1530;
--bloom: .4;
}
}
.cb-05[data-theme="light"] {
--bg: #f5f4fb;
--body: #1a1530;
--bloom: .4;
}
.cb-05,
.cb-05 *,
.cb-05 *::before,
.cb-05 *::after {
box-sizing: border-box;
}
.cb-05__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.5rem,6vw,4rem);
}
.cb-05__btn {
position: relative;
isolation: isolate;
cursor: pointer;
display: grid;
grid-auto-flow: column;
align-items: center;
gap: .55rem;
min-height: 3.5rem;
padding: 0 1.9rem;
border: 0;
border-radius: var(--radius);
font: inherit;
font-size: 1rem;
font-weight: 600;
letter-spacing: -.005em;
color: var(--ink);
background: radial-gradient(120% 140% at 15% 0%, color-mix(in oklab,var(--c1) 55%,transparent), transparent 60%), radial-gradient(120% 140% at 90% 100%, color-mix(in oklab,var(--c2) 35%,transparent), transparent 60%), var(--body);
background-size: 150% 150%,150% 150%,100% 100%;
background-position: 0% 0%,100% 100%,0 0;
transition: translate .3s cubic-bezier(.16,1,.3,1), background-position .6s cubic-bezier(.16,1,.3,1), scale .18s ease;
}
.cb-05__btn svg {
width: 1.1rem;
height: 1.1rem;
color: var(--c2);
}
.cb-05__btn::before,
.cb-05__btn::after {
content: '';
position: absolute;
inset: -2px;
z-index: -1;
border-radius: inherit;
background: conic-gradient(from var(--cb-05-a),var(--c1),var(--c2),var(--c3),var(--c1));
animation: cb-05-spin 4.5s linear infinite;
}
.cb-05__btn::after {
inset: 2px;
filter: blur(20px);
opacity: var(--bloom);
transition: opacity .35s ease;
}
@keyframes cb-05-spin {
to {
--cb-05-a: 495deg;
}
}
.cb-05__btn:hover {
translate: 0 -2px;
background-position: 30% 20%,70% 80%,0 0;
}
.cb-05__btn:hover::after {
opacity: calc(var(--bloom) + .35);
}
.cb-05__btn:active {
scale: .98;
translate: 0 0;
}
.cb-05__btn:focus-visible {
outline: 2px solid var(--c2);
outline-offset: 5px;
}
.cb-05__btn:disabled {
cursor: not-allowed;
filter: grayscale(.85);
opacity: .55;
}
.cb-05__btn:disabled::before,
.cb-05__btn:disabled::after {
animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) {
.cb-05__btn::before,
.cb-05__btn::after {
animation: none;
}
.cb-05__btn {
transition: none;
}
}
@media (forced-colors: active) {
.cb-05__btn {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
}
.cb-05__btn::before,
.cb-05__btn::after {
display: none;
}
}
```How this works
Registering the property gives the browser a type it knows how to interpolate, which turns an un-animatable gradient into a smooth one:
@property --cb-05-a{ syntax:'<angle>'; inherits:false; initial-value:0deg; }
.cb-05__btn::before{
content:''; position:absolute; inset:-2px; border-radius:inherit; z-index:-1;
background:conic-gradient(from var(--cb-05-a),
var(--c1),var(--c2),var(--c3),var(--c1));
animation:cb-05-spin 4.5s linear infinite;
}
@keyframes cb-05-spin{ to{ --cb-05-a:360deg; } }The ring is a slightly larger layer behind the button, so the button's own opaque background masks the middle and only a 2px rim shows — cheaper and rounder than a border-image. A second copy of the same layer, blurred with filter:blur(18px) and opacity, is the bloom; on hover the opacity rises and the button lifts.
The body itself is two overlapping radial gradients whose background-position shifts on hover, so the surface feels lit rather than flat.
Make it yours
- Recolour with the three stop tokens
--c1/--c2/--c3— keep them within ~120° of hue for a premium look, spread them wide for a playful one. - Speed:
4.5sreads as premium; under 2s it looks like a loading state, which is a usability bug on a CTA. - Thicken the ring by changing
inset:-2pxto-3px; the radius follows automatically viaborder-radius:inherit. - Turn the bloom off for dark UI density by setting
--bloom:0; hover still raises it. - For a static gradient (no motion budget) delete the animation and keep
from 135deg— the ring still reads as premium.
Gotchas — read before shipping
- A custom property is a token string until you register it;
@propertywith the rightsyntaxis mandatory or the keyframes simply snap at 50%. - Infinite motion on a primary CTA is an accessibility risk — the
prefers-reduced-motionblock here stops the spin and keeps a fixed-angle gradient, which is not optional. filter:blur()on a large layer is fill-rate heavy; keep the bloom element at the button's size, never full-viewport, and never animate the blur radius itself.- Gradient text on gradient fills destroys contrast. Keep the label a solid colour with ≥4.5:1 against the darkest gradient stop.
- Put the ring at
z-index:-1withisolation:isolateon the button — without isolation the negative index can slip behind the page background in some stacking contexts.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Floor set by oklch(), color-mix(), @property as this demo is written. The core technique itself goes back further — Chrome 85+.
@property needs Chrome 85 / Safari 16.4 / Firefox 128; older engines show the ring at its initial angle without spinning (fully usable). conic-gradient is universal since 2020.