34 CSS Floating Buttons25 / 34

Pure CSSMIT licensed

Classic Gradient Circle Plus FAB CSS

The one everybody needs first: a round gradient FAB with a plus, done to a standard you can ship. The plus is drawn with two pseudo-elements rather than an icon file, so it rotates into an X, scales with the button and inherits colour. Two rules of CSS, three states, and a shadow that changes with elevation instead of staying flat.

Published Updated

Live Demo
Try it

The code

<section class="fb-25" aria-label="Classic gradient circle plus floating action button demo">
  <div class="fb-25__stage">
    <p class="fb-25__eyebrow">pseudo-element icon · elevation states</p>
    <h2 class="fb-25__title">The plus is two rectangles</h2>
    <p class="fb-25__sub">No icon file, no font, no flash of missing glyph. It inherits colour, scales with the button, and rotates into a close mark on hover.</p>
    <div class="fb-25__row">
      <div class="fb-25__cell"><button class="fb-25__fab fb-25__fab--sm" type="button" aria-label="Add item, compact"></button><span class="fb-25__cap">48</span></div>
      <div class="fb-25__cell"><button class="fb-25__fab" type="button" aria-label="Add item"></button><span class="fb-25__cap">56</span></div>
      <div class="fb-25__cell"><button class="fb-25__fab fb-25__fab--lg" type="button" aria-label="Add item, prominent"></button><span class="fb-25__cap">64</span></div>
      <div class="fb-25__cell"><button class="fb-25__fab fb-25__fab--alt" type="button" aria-label="Add item, alternate palette"></button><span class="fb-25__cap">alt</span></div>
      <div class="fb-25__cell"><button class="fb-25__fab fb-25__fab--out" type="button" aria-label="Add item, outlined"></button><span class="fb-25__cap">outline</span></div>
    </div>
    <div class="fb-25__states" aria-hidden="true">
      <span><i class="fb-25__sw fb-25__sw--rest"></i>rest · 10/24</span>
      <span><i class="fb-25__sw fb-25__sw--hov"></i>hover · 16/36 −2px</span>
      <span><i class="fb-25__sw fb-25__sw--act"></i>active · 4/10 ×0.94</span>
    </div>
    <p class="fb-25__chip">currentColor bars · 150° gradient · hue-matched shadow</p>
  </div>
</section>
.fb-25 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--fb-25-bg);
  --fb-25-bg: oklch(0.97 0.01 300);
  --fb-25-a: oklch(0.7 0.19 330);
  --fb-25-b: oklch(0.5 0.2 288);
  --fb-25-ink: oklch(0.23 0.03 300);
  --fb-25-mut: oklch(0.53 0.02 300);
  --fb-25-size: 56px;
  font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
  color: var(--fb-25-ink);
  display: grid;
  place-items: center;
  padding: clamp(20px,5vw,56px);
}

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

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

.fb-25__eyebrow {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--fb-25-b);
}

.fb-25__title {
  font-size: clamp(26px,4.6vw,44px);
  line-height: 1.05;
  letter-spacing: -.03em;
  font-weight: 700;
  text-wrap: balance;
}

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

.fb-25__row {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(16px,4vw,34px);
  justify-content: center;
  align-items: center;
}

.fb-25__cell {
  display: grid;
  gap: 10px;
  justify-items: center;
}

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

.fb-25__fab {
  position: relative;
  inline-size: var(--fb-25-size);
  block-size: var(--fb-25-size);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  color: oklch(0.99 0.01 330);
  background: linear-gradient(150deg,var(--fb-25-a),var(--fb-25-b));
  box-shadow: 0 10px 24px -10px oklch(0.5 0.2 288/.75),0 2px 5px oklch(0.4 0.12 300/.25);
  transition: transform .3s cubic-bezier(.2,1.25,.4,1),box-shadow .3s ease,rotate .34s cubic-bezier(.3,1.3,.4,1);
}

.fb-25__fab::before,
.fb-25__fab::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  inline-size: calc(var(--fb-25-size) * .4);
  block-size: 2.6px;
  translate: -50% -50%;
  border-radius: 2px;
  background: currentColor;
}

.fb-25__fab::after {
  rotate: 90deg;
}

.fb-25__fab:hover {
  rotate: 90deg;
  box-shadow: 0 16px 36px -10px oklch(0.5 0.2 288/.85),0 2px 5px oklch(0.4 0.12 300/.25);
  translate: 0 -2px;
}

.fb-25__fab:active {
  transform: scale(.94);
  box-shadow: 0 4px 10px -4px oklch(0.5 0.2 288/.7);
}

.fb-25__fab:focus-visible {
  outline: 3px solid var(--fb-25-b);
  outline-offset: 3px;
}

.fb-25__fab--sm {
  --fb-25-size: 48px;
}

.fb-25__fab--lg {
  --fb-25-size: 64px;
}

.fb-25__fab--alt {
  --fb-25-a: oklch(0.82 0.16 90);
  --fb-25-b: oklch(0.6 0.18 35);
  color: oklch(0.2 0.05 45);
  box-shadow: 0 10px 24px -10px oklch(0.6 0.18 35/.7),0 2px 5px oklch(0.4 0.1 45/.22);
}

.fb-25__fab--alt:hover {
  box-shadow: 0 16px 36px -10px oklch(0.6 0.18 35/.8);
}

.fb-25__fab--out {
  background: oklch(1 0 0);
  color: var(--fb-25-b);
  box-shadow: 0 10px 24px -14px oklch(0.5 0.2 288/.7),inset 0 0 0 2px color-mix(in oklch,var(--fb-25-b) 55%,transparent);
}

.fb-25__fab--out:hover {
  background: color-mix(in oklch,var(--fb-25-b) 8%,oklch(1 0 0));
  box-shadow: 0 16px 34px -14px oklch(0.5 0.2 288/.8),inset 0 0 0 2px var(--fb-25-b);
}

.fb-25__states {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  justify-content: center;
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11px;
  color: var(--fb-25-mut);
}

.fb-25__states span {
  display: flex;
  align-items: center;
  gap: 7px;
}

.fb-25__sw {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: linear-gradient(150deg,var(--fb-25-a),var(--fb-25-b));
}

.fb-25__sw--rest {
  box-shadow: 0 6px 14px -6px oklch(0.5 0.2 288/.75);
}

.fb-25__sw--hov {
  box-shadow: 0 10px 22px -6px oklch(0.5 0.2 288/.85);
  translate: 0 -2px;
}

.fb-25__sw--act {
  box-shadow: 0 2px 6px -2px oklch(0.5 0.2 288/.7);
  scale: .94;
}

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

@media (prefers-reduced-motion: reduce) {
  .fb-25__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: Classic Gradient Circle Plus FAB CSS
Source: https://codefronts.com/components/css-floating-buttons/fb01/

The one everybody needs first: a round gradient FAB with a plus, done to a standard you can ship. The plus is drawn with two pseudo-elements rather than an icon file, so it rotates into an X, scales with the button and inherits colour. Two rules of CSS, three states, and a shadow that changes with elevation instead of staying flat.
## HTML
```html
<section class="fb-25" aria-label="Classic gradient circle plus floating action button demo">
  <div class="fb-25__stage">
    <p class="fb-25__eyebrow">pseudo-element icon · elevation states</p>
    <h2 class="fb-25__title">The plus is two rectangles</h2>
    <p class="fb-25__sub">No icon file, no font, no flash of missing glyph. It inherits colour, scales with the button, and rotates into a close mark on hover.</p>
    <div class="fb-25__row">
      <div class="fb-25__cell"><button class="fb-25__fab fb-25__fab--sm" type="button" aria-label="Add item, compact"></button><span class="fb-25__cap">48</span></div>
      <div class="fb-25__cell"><button class="fb-25__fab" type="button" aria-label="Add item"></button><span class="fb-25__cap">56</span></div>
      <div class="fb-25__cell"><button class="fb-25__fab fb-25__fab--lg" type="button" aria-label="Add item, prominent"></button><span class="fb-25__cap">64</span></div>
      <div class="fb-25__cell"><button class="fb-25__fab fb-25__fab--alt" type="button" aria-label="Add item, alternate palette"></button><span class="fb-25__cap">alt</span></div>
      <div class="fb-25__cell"><button class="fb-25__fab fb-25__fab--out" type="button" aria-label="Add item, outlined"></button><span class="fb-25__cap">outline</span></div>
    </div>
    <div class="fb-25__states" aria-hidden="true">
      <span><i class="fb-25__sw fb-25__sw--rest"></i>rest · 10/24</span>
      <span><i class="fb-25__sw fb-25__sw--hov"></i>hover · 16/36 −2px</span>
      <span><i class="fb-25__sw fb-25__sw--act"></i>active · 4/10 ×0.94</span>
    </div>
    <p class="fb-25__chip">currentColor bars · 150° gradient · hue-matched shadow</p>
  </div>
</section>
```
## CSS
```css
.fb-25 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--fb-25-bg);
  --fb-25-bg: oklch(0.97 0.01 300);
  --fb-25-a: oklch(0.7 0.19 330);
  --fb-25-b: oklch(0.5 0.2 288);
  --fb-25-ink: oklch(0.23 0.03 300);
  --fb-25-mut: oklch(0.53 0.02 300);
  --fb-25-size: 56px;
  font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
  color: var(--fb-25-ink);
  display: grid;
  place-items: center;
  padding: clamp(20px,5vw,56px);
}

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

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

.fb-25__eyebrow {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--fb-25-b);
}

.fb-25__title {
  font-size: clamp(26px,4.6vw,44px);
  line-height: 1.05;
  letter-spacing: -.03em;
  font-weight: 700;
  text-wrap: balance;
}

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

.fb-25__row {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(16px,4vw,34px);
  justify-content: center;
  align-items: center;
}

.fb-25__cell {
  display: grid;
  gap: 10px;
  justify-items: center;
}

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

.fb-25__fab {
  position: relative;
  inline-size: var(--fb-25-size);
  block-size: var(--fb-25-size);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  color: oklch(0.99 0.01 330);
  background: linear-gradient(150deg,var(--fb-25-a),var(--fb-25-b));
  box-shadow: 0 10px 24px -10px oklch(0.5 0.2 288/.75),0 2px 5px oklch(0.4 0.12 300/.25);
  transition: transform .3s cubic-bezier(.2,1.25,.4,1),box-shadow .3s ease,rotate .34s cubic-bezier(.3,1.3,.4,1);
}

.fb-25__fab::before,
.fb-25__fab::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  inline-size: calc(var(--fb-25-size) * .4);
  block-size: 2.6px;
  translate: -50% -50%;
  border-radius: 2px;
  background: currentColor;
}

.fb-25__fab::after {
  rotate: 90deg;
}

.fb-25__fab:hover {
  rotate: 90deg;
  box-shadow: 0 16px 36px -10px oklch(0.5 0.2 288/.85),0 2px 5px oklch(0.4 0.12 300/.25);
  translate: 0 -2px;
}

.fb-25__fab:active {
  transform: scale(.94);
  box-shadow: 0 4px 10px -4px oklch(0.5 0.2 288/.7);
}

.fb-25__fab:focus-visible {
  outline: 3px solid var(--fb-25-b);
  outline-offset: 3px;
}

.fb-25__fab--sm {
  --fb-25-size: 48px;
}

.fb-25__fab--lg {
  --fb-25-size: 64px;
}

.fb-25__fab--alt {
  --fb-25-a: oklch(0.82 0.16 90);
  --fb-25-b: oklch(0.6 0.18 35);
  color: oklch(0.2 0.05 45);
  box-shadow: 0 10px 24px -10px oklch(0.6 0.18 35/.7),0 2px 5px oklch(0.4 0.1 45/.22);
}

.fb-25__fab--alt:hover {
  box-shadow: 0 16px 36px -10px oklch(0.6 0.18 35/.8);
}

.fb-25__fab--out {
  background: oklch(1 0 0);
  color: var(--fb-25-b);
  box-shadow: 0 10px 24px -14px oklch(0.5 0.2 288/.7),inset 0 0 0 2px color-mix(in oklch,var(--fb-25-b) 55%,transparent);
}

.fb-25__fab--out:hover {
  background: color-mix(in oklch,var(--fb-25-b) 8%,oklch(1 0 0));
  box-shadow: 0 16px 34px -14px oklch(0.5 0.2 288/.8),inset 0 0 0 2px var(--fb-25-b);
}

.fb-25__states {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  justify-content: center;
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11px;
  color: var(--fb-25-mut);
}

.fb-25__states span {
  display: flex;
  align-items: center;
  gap: 7px;
}

.fb-25__sw {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: linear-gradient(150deg,var(--fb-25-a),var(--fb-25-b));
}

.fb-25__sw--rest {
  box-shadow: 0 6px 14px -6px oklch(0.5 0.2 288/.75);
}

.fb-25__sw--hov {
  box-shadow: 0 10px 22px -6px oklch(0.5 0.2 288/.85);
  translate: 0 -2px;
}

.fb-25__sw--act {
  box-shadow: 0 2px 6px -2px oklch(0.5 0.2 288/.7);
  scale: .94;
}

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

@media (prefers-reduced-motion: reduce) {
  .fb-25__fab {
    transition: none;
  }
}
```

How this works

No SVG, no icon font — the plus is the button's own pseudo-elements:

.fab::before, .fab::after{ content:""; position:absolute;
  background:currentColor; border-radius:2px;
  inline-size:var(--bar); block-size:2.5px; }        /* horizontal */
.fab::after{ rotate:90deg }                            /* vertical  */
.fab:hover{ rotate:90deg }                             /* whole icon spins */

Because both bars use currentColor and a size derived from a custom property, changing --fb-25-size scales the icon proportionally, and changing the text colour recolours it. That is three fewer files than the icon-font approach and it never suffers a flash of missing glyph.

The gradient runs at 150° rather than a flat vertical, so the highlight lands at the top-left where the implied light source is — matching the shadow below it. A vertical gradient with a bottom-heavy shadow is the most common giveaway of a rushed button.

Elevation is a state, not a constant: rest is a 2-shadow stack at 10/24px, hover lifts to 16/36px with a 2px translate, and active drops to 4/10px with a 0.94 scale. Buttons that keep one shadow through every state feel like images rather than controls.

Make it yours

  • Icon swap: any 24px SVG drops into the same button; keep the pseudo-element version for plus/close since it can rotate between them.
  • Sizes: 48px compact, 56px standard, 64px prominent. Update the bar length with the same token so the icon stays at 40% of the button.
  • Colour: the two gradient stops plus the shadow hue are the only theme values. Match the shadow to the darker stop, never to black.
  • Outlined variant: transparent background, 2px gradient border via a background-clip trick, coloured text — the secondary action next to this one.

Gotchas — read before shipping

  • A pseudo-element icon requires the button to be position:relative with the bars absolutely centred; forgetting the centring shifts the plus by a pixel and it looks broken at small sizes.
  • Gradients on very small buttons band visibly. Below ~40px use a solid colour or add a 2% noise overlay.
  • Do not put the rotation transition on the whole button if it has a shadow — the shadow rotates too. Rotate an inner wrapper or accept a symmetrical shadow.
  • Rounded buttons still need a focus outline. outline-offset:3px on a circle looks intentional; a clipped outline does not.

Browser support

ChromeSafariFirefoxEdge
114+17.5+121+114+

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

Everything here except the oklch() tokens has been supported since 2015. Substituting hex colours makes this demo work in effectively any browser in use.

Search CodeFronts

Loading…