34 CSS Floating Buttons32 / 34

Pure CSSMIT licensed

Aurora Gradient Drift Premium Floating Button CSS

The "premium tier" button: three soft radial blobs drifting at different speeds behind a dark core, blended so the colours mix like light rather than stacking like paint. Registered custom properties make the blob positions animatable, and a slow shimmer sweeps the rim. This is what an AI or upgrade action should look like in 2027 — luminous, but calm.

Published Updated

Live Demo
Try it

The code

<section class="fb-32" aria-label="Aurora gradient drift floating button demo">
  <div class="fb-32__stage">
    <p class="fb-32__eyebrow">@property &lt;percentage&gt; · non-looping drift</p>
    <h2 class="fb-32__title">Light, not paint</h2>
    <p class="fb-32__sub">Three blobs on 14, 19 and 25 second cycles that never line up, blurred once, behind a dark core. The colour escapes around the edges.</p>
    <div class="fb-32__row">
      <div class="fb-32__cell">
        <button class="fb-32__fab" type="button" aria-label="Upgrade to premium">
          <span class="fb-32__aurora" aria-hidden="true"></span>
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 3l2.1 5.4L19.5 10l-5.4 1.6L12 17l-2.1-5.4L4.5 10l5.4-1.6z" fill="currentColor"/><path d="M18.5 15l.8 2.1 2.2.8-2.2.8-.8 2.1-.8-2.1-2.2-.8 2.2-.8z" fill="currentColor" opacity=".7"/></svg>
        </button>
        <span class="fb-32__cap">drift</span>
      </div>
      <div class="fb-32__cell">
        <button class="fb-32__fab fb-32__fab--pill" type="button">
          <span class="fb-32__aurora" aria-hidden="true"></span>
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 3l2.1 5.4L19.5 10l-5.4 1.6L12 17l-2.1-5.4L4.5 10l5.4-1.6z" fill="currentColor"/></svg>
          <b>Ask the model</b>
        </button>
        <span class="fb-32__cap">pill · rim shimmer</span>
      </div>
      <div class="fb-32__cell">
        <button class="fb-32__fab fb-32__fab--calm" type="button" aria-label="Upgrade, static composition">
          <span class="fb-32__aurora" aria-hidden="true"></span>
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M5 12h14M13 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
        </button>
        <span class="fb-32__cap">static</span>
      </div>
    </div>
    <p class="fb-32__chip">blur once · core above · rim at 40% opacity</p>
  </div>
</section>
@property --fb-32-x1 {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 22%;
}

@property --fb-32-y1 {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 28%;
}

@property --fb-32-x2 {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 74%;
}

@property --fb-32-y2 {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 34%;
}

@property --fb-32-x3 {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 46%;
}

@property --fb-32-y3 {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 78%;
}

@property --fb-32-ang {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.fb-32 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--fb-32-bg);
  --fb-32-bg: radial-gradient(120% 90% at 50% 8%,oklch(0.24 0.04 285),oklch(0.13 0.015 280) 68%);
  --fb-32-ink: oklch(0.97 0.008 285);
  --fb-32-mut: oklch(0.75 0.015 285);
  --fb-32-core: oklch(0.19 0.025 285);
  font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
  color: var(--fb-32-ink);
  display: grid;
  place-items: center;
  padding: clamp(20px,5vw,56px);
}

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

.fb-32__stage {
  width: min(100%,60ch);
  display: grid;
  gap: clamp(18px,3vw,30px);
  justify-items: center;
  text-align: center;
}

.fb-32__eyebrow {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: oklch(0.8 0.13 300);
}

.fb-32__title {
  font-size: clamp(27px,5vw,48px);
  line-height: 1.04;
  letter-spacing: -.035em;
  font-weight: 700;
  text-wrap: balance;
}

.fb-32__sub {
  max-width: 50ch;
  font-size: clamp(13.5px,1.7vw,16.5px);
  line-height: 1.62;
  color: var(--fb-32-mut);
  text-wrap: pretty;
}

.fb-32__row {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(22px,6vw,48px);
  justify-content: center;
  align-items: center;
  padding: 10px 0;
}

.fb-32__cell {
  display: grid;
  gap: 12px;
  justify-items: center;
}

.fb-32__cap {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11px;
  color: var(--fb-32-mut);
}

.fb-32__fab {
  position: relative;
  isolation: isolate;
  display: grid;
  place-items: center;
  inline-size: 78px;
  block-size: 78px;
  border: 1px solid oklch(1 0 0/.12);
  border-radius: 26px;
  cursor: pointer;
  color: var(--fb-32-ink);
  background: var(--fb-32-core);
  overflow: hidden;
  box-shadow: 0 20px 50px -18px oklch(0.55 0.2 300/.55),0 3px 10px oklch(0.06 0.01 285/.8);
  transition: transform .32s cubic-bezier(.2,1.25,.4,1),box-shadow .32s ease,border-color .32s ease;
}

.fb-32__fab svg {
  width: 30px;
  height: 30px;
  position: relative;
  z-index: 2;
  filter: drop-shadow(0 1px 6px oklch(0.6 0.2 300/.7));
}

.fb-32__aurora {
  position: absolute;
  inset: -32%;
  z-index: 1;
  filter: blur(16px) saturate(155%);
  opacity: .95;
  background: radial-gradient(42% 54% at var(--fb-32-x1) var(--fb-32-y1),oklch(0.66 0.24 295) 0%,transparent 70%),radial-gradient(38% 48% at var(--fb-32-x2) var(--fb-32-y2),oklch(0.78 0.16 195) 0%,transparent 68%),radial-gradient(50% 58% at var(--fb-32-x3) var(--fb-32-y3),oklch(0.7 0.22 345) 0%,transparent 72%);
  animation: fb-32-d1 14s ease-in-out infinite alternate,fb-32-d2 19s ease-in-out infinite alternate,fb-32-d3 25s ease-in-out infinite alternate;
}

@keyframes fb-32-d1 {
  to {
    --fb-32-x1: 76%;
    --fb-32-y1: 66%;
  }
}

@keyframes fb-32-d2 {
  to {
    --fb-32-x2: 24%;
    --fb-32-y2: 74%;
  }
}

@keyframes fb-32-d3 {
  to {
    --fb-32-x3: 72%;
    --fb-32-y3: 22%;
  }
}

.fb-32__fab::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
  padding: 1.4px;
  background: conic-gradient(from var(--fb-32-ang),transparent 0deg,oklch(0.9 0.12 300/.75) 40deg,transparent 110deg,transparent 360deg);
  -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;
  opacity: .4;
  animation: fb-32-rim 8s linear infinite;
  pointer-events: none;
}

@keyframes fb-32-rim {
  to {
    --fb-32-ang: 360deg;
  }
}

.fb-32__fab:hover {
  transform: translateY(-3px);
  border-color: oklch(1 0 0/.22);
  box-shadow: 0 28px 66px -18px oklch(0.55 0.2 300/.7),0 3px 10px oklch(0.06 0.01 285/.8);
}

.fb-32__fab:active {
  transform: scale(.95);
}

.fb-32__fab:focus-visible {
  outline: 3px solid oklch(0.86 0.12 300);
  outline-offset: 5px;
}

.fb-32__fab--pill {
  inline-size: auto;
  block-size: 70px;
  border-radius: 99px;
  padding-inline: 26px;
  grid-auto-flow: column;
  gap: 12px;
  font-family: inherit;
  font-size: 16.5px;
  font-weight: 640;
  letter-spacing: -.012em;
}

.fb-32__fab--pill svg {
  width: 24px;
  height: 24px;
}

.fb-32__fab--pill b {
  position: relative;
  z-index: 2;
  font-weight: 640;
}

.fb-32__fab--calm .fb-32__aurora {
  animation: none;
}

.fb-32__fab--calm::after {
  animation: none;
  opacity: .28;
}

.fb-32__chip {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11px;
  color: var(--fb-32-mut);
  padding: 8px 14px;
  border: 1px solid oklch(1 0 0/.16);
  border-radius: 99px;
}

@media (prefers-reduced-motion: reduce) {
  .fb-32__aurora,
    .fb-32__fab::after {
    animation: none !important;
  }

  .fb-32__fab {
    transition: none;
  }
}
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 Floating 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: Aurora Gradient Drift Premium Floating Button CSS
Source: https://codefronts.com/components/css-floating-buttons/fb14/

The "premium tier" button: three soft radial blobs drifting at different speeds behind a dark core, blended so the colours mix like light rather than stacking like paint. Registered custom properties make the blob positions animatable, and a slow shimmer sweeps the rim. This is what an AI or upgrade action should look like in 2027 — luminous, but calm.
## HTML
```html
<section class="fb-32" aria-label="Aurora gradient drift floating button demo">
  <div class="fb-32__stage">
    <p class="fb-32__eyebrow">@property &lt;percentage&gt; · non-looping drift</p>
    <h2 class="fb-32__title">Light, not paint</h2>
    <p class="fb-32__sub">Three blobs on 14, 19 and 25 second cycles that never line up, blurred once, behind a dark core. The colour escapes around the edges.</p>
    <div class="fb-32__row">
      <div class="fb-32__cell">
        <button class="fb-32__fab" type="button" aria-label="Upgrade to premium">
          <span class="fb-32__aurora" aria-hidden="true"></span>
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 3l2.1 5.4L19.5 10l-5.4 1.6L12 17l-2.1-5.4L4.5 10l5.4-1.6z" fill="currentColor"/><path d="M18.5 15l.8 2.1 2.2.8-2.2.8-.8 2.1-.8-2.1-2.2-.8 2.2-.8z" fill="currentColor" opacity=".7"/></svg>
        </button>
        <span class="fb-32__cap">drift</span>
      </div>
      <div class="fb-32__cell">
        <button class="fb-32__fab fb-32__fab--pill" type="button">
          <span class="fb-32__aurora" aria-hidden="true"></span>
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 3l2.1 5.4L19.5 10l-5.4 1.6L12 17l-2.1-5.4L4.5 10l5.4-1.6z" fill="currentColor"/></svg>
          <b>Ask the model</b>
        </button>
        <span class="fb-32__cap">pill · rim shimmer</span>
      </div>
      <div class="fb-32__cell">
        <button class="fb-32__fab fb-32__fab--calm" type="button" aria-label="Upgrade, static composition">
          <span class="fb-32__aurora" aria-hidden="true"></span>
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M5 12h14M13 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
        </button>
        <span class="fb-32__cap">static</span>
      </div>
    </div>
    <p class="fb-32__chip">blur once · core above · rim at 40% opacity</p>
  </div>
</section>
```
## CSS
```css
@property --fb-32-x1 {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 22%;
}

@property --fb-32-y1 {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 28%;
}

@property --fb-32-x2 {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 74%;
}

@property --fb-32-y2 {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 34%;
}

@property --fb-32-x3 {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 46%;
}

@property --fb-32-y3 {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 78%;
}

@property --fb-32-ang {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.fb-32 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--fb-32-bg);
  --fb-32-bg: radial-gradient(120% 90% at 50% 8%,oklch(0.24 0.04 285),oklch(0.13 0.015 280) 68%);
  --fb-32-ink: oklch(0.97 0.008 285);
  --fb-32-mut: oklch(0.75 0.015 285);
  --fb-32-core: oklch(0.19 0.025 285);
  font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
  color: var(--fb-32-ink);
  display: grid;
  place-items: center;
  padding: clamp(20px,5vw,56px);
}

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

.fb-32__stage {
  width: min(100%,60ch);
  display: grid;
  gap: clamp(18px,3vw,30px);
  justify-items: center;
  text-align: center;
}

.fb-32__eyebrow {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: oklch(0.8 0.13 300);
}

.fb-32__title {
  font-size: clamp(27px,5vw,48px);
  line-height: 1.04;
  letter-spacing: -.035em;
  font-weight: 700;
  text-wrap: balance;
}

.fb-32__sub {
  max-width: 50ch;
  font-size: clamp(13.5px,1.7vw,16.5px);
  line-height: 1.62;
  color: var(--fb-32-mut);
  text-wrap: pretty;
}

.fb-32__row {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(22px,6vw,48px);
  justify-content: center;
  align-items: center;
  padding: 10px 0;
}

.fb-32__cell {
  display: grid;
  gap: 12px;
  justify-items: center;
}

.fb-32__cap {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11px;
  color: var(--fb-32-mut);
}

.fb-32__fab {
  position: relative;
  isolation: isolate;
  display: grid;
  place-items: center;
  inline-size: 78px;
  block-size: 78px;
  border: 1px solid oklch(1 0 0/.12);
  border-radius: 26px;
  cursor: pointer;
  color: var(--fb-32-ink);
  background: var(--fb-32-core);
  overflow: hidden;
  box-shadow: 0 20px 50px -18px oklch(0.55 0.2 300/.55),0 3px 10px oklch(0.06 0.01 285/.8);
  transition: transform .32s cubic-bezier(.2,1.25,.4,1),box-shadow .32s ease,border-color .32s ease;
}

.fb-32__fab svg {
  width: 30px;
  height: 30px;
  position: relative;
  z-index: 2;
  filter: drop-shadow(0 1px 6px oklch(0.6 0.2 300/.7));
}

.fb-32__aurora {
  position: absolute;
  inset: -32%;
  z-index: 1;
  filter: blur(16px) saturate(155%);
  opacity: .95;
  background: radial-gradient(42% 54% at var(--fb-32-x1) var(--fb-32-y1),oklch(0.66 0.24 295) 0%,transparent 70%),radial-gradient(38% 48% at var(--fb-32-x2) var(--fb-32-y2),oklch(0.78 0.16 195) 0%,transparent 68%),radial-gradient(50% 58% at var(--fb-32-x3) var(--fb-32-y3),oklch(0.7 0.22 345) 0%,transparent 72%);
  animation: fb-32-d1 14s ease-in-out infinite alternate,fb-32-d2 19s ease-in-out infinite alternate,fb-32-d3 25s ease-in-out infinite alternate;
}

@keyframes fb-32-d1 {
  to {
    --fb-32-x1: 76%;
    --fb-32-y1: 66%;
  }
}

@keyframes fb-32-d2 {
  to {
    --fb-32-x2: 24%;
    --fb-32-y2: 74%;
  }
}

@keyframes fb-32-d3 {
  to {
    --fb-32-x3: 72%;
    --fb-32-y3: 22%;
  }
}

.fb-32__fab::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
  padding: 1.4px;
  background: conic-gradient(from var(--fb-32-ang),transparent 0deg,oklch(0.9 0.12 300/.75) 40deg,transparent 110deg,transparent 360deg);
  -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;
  opacity: .4;
  animation: fb-32-rim 8s linear infinite;
  pointer-events: none;
}

@keyframes fb-32-rim {
  to {
    --fb-32-ang: 360deg;
  }
}

.fb-32__fab:hover {
  transform: translateY(-3px);
  border-color: oklch(1 0 0/.22);
  box-shadow: 0 28px 66px -18px oklch(0.55 0.2 300/.7),0 3px 10px oklch(0.06 0.01 285/.8);
}

.fb-32__fab:active {
  transform: scale(.95);
}

.fb-32__fab:focus-visible {
  outline: 3px solid oklch(0.86 0.12 300);
  outline-offset: 5px;
}

.fb-32__fab--pill {
  inline-size: auto;
  block-size: 70px;
  border-radius: 99px;
  padding-inline: 26px;
  grid-auto-flow: column;
  gap: 12px;
  font-family: inherit;
  font-size: 16.5px;
  font-weight: 640;
  letter-spacing: -.012em;
}

.fb-32__fab--pill svg {
  width: 24px;
  height: 24px;
}

.fb-32__fab--pill b {
  position: relative;
  z-index: 2;
  font-weight: 640;
}

.fb-32__fab--calm .fb-32__aurora {
  animation: none;
}

.fb-32__fab--calm::after {
  animation: none;
  opacity: .28;
}

.fb-32__chip {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11px;
  color: var(--fb-32-mut);
  padding: 8px 14px;
  border: 1px solid oklch(1 0 0/.16);
  border-radius: 99px;
}

@media (prefers-reduced-motion: reduce) {
  .fb-32__aurora,
    .fb-32__fab::after {
    animation: none !important;
  }

  .fb-32__fab {
    transition: none;
  }
}
```

How this works

Registered percentage properties let radial-gradient positions animate:

@property --x1{ syntax:"<percentage>"; inherits:false; initial-value:20% }
.aurora{ background:
  radial-gradient(42% 55% at var(--x1) var(--y1), #7c5cff 0%, transparent 70%),
  radial-gradient(38% 48% at var(--x2) var(--y2), #23d3ee 0%, transparent 68%),
  radial-gradient(50% 60% at var(--x3) var(--y3), #ff5ca8 0%, transparent 72%);
  filter:blur(14px) saturate(150%); }
@keyframes drift{ 50%{ --x1:78%; --y1:64%; --x2:26%; --y2:22%; --x3:60%; --y3:80% } }

Three blobs, three durations (14s, 19s, 25s) that share no common multiple, so the pattern never visibly loops. Prime-ish durations are the cheapest way to make procedural motion feel non-repeating.

The blur is applied once to the aurora layer, not to the button — blurring the button would soften the icon. The core sits above the aurora with a near-opaque dark fill, so the colour reads as light escaping around and through the edges rather than as a painted gradient.

The rim shimmer is a conic-gradient border rotating at 8s, at only 40% opacity. Restraint is what makes this read as premium: if you can see the individual colours moving, it has gone too far.

Make it yours

  • Palette: three hues roughly 120° apart give the classic aurora; two adjacent hues plus one accent reads more corporate.
  • Calm mode: pause the drift and keep a static composition — still beautiful, zero ongoing cost.
  • Blob size: larger, softer blobs (55–65%) read as ambience; smaller, tighter ones read as energy.
  • Light theme: raise the core's lightness above 0.9 and drop the blob opacity to ~35% for aurora-on-white.

Gotchas — read before shipping

  • Animating filter:blur is very expensive. Blur the layer once and animate only the gradient positions underneath it.
  • Three animated gradients on one element can cost real GPU memory at large sizes. Keep the aurora element under about 200×200px.
  • Without @property the position keyframes do not interpolate — you get a slideshow instead of a drift.
  • Long-running infinite animations drain battery on mobile. Pause them when the element leaves the viewport, or under prefers-reduced-motion.

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 111+.

@property with <percentage> syntax: Chrome 85+, Safari 16.4+, Firefox 128+. In older Firefox the blobs hold their initial positions — the button still looks intentional, it just does not drift.

Search CodeFronts

Loading…