34 CSS Floating Buttons25 / 34
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
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><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;
}
}.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;
}
}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;
}
}
```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
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 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.