51 CSS Buttons29 / 51

Light JSMIT licensed

CSS Holographic Foil Sticker Button

A die-cut vinyl sticker whose holographic laminate shifts with the pointer: the rainbow sweep, the specular sheen and a 3D tilt all read from two pointer variables, and the corner peels up as you approach — the effect trading-card collectors call "cracked ice".

Published Updated

Live Demo
Try it

The code

<div class="cb-29">
  <div class="cb-29__stage">
    <button class="cb-29__btn" type="button">
      <span class="cb-29__holo" aria-hidden="true"></span>
      <span class="cb-29__peel" aria-hidden="true"></span>
      <span class="cb-29__plate">
        <span class="cb-29__label">Limited drop</span>
        <span class="cb-29__sub">01 / 250</span>
      </span>
    </button>
  </div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Archivo:wght@600;700;800&display=swap');

.cb-29 {
  --bg: #0a0a0f;
  --cut: #fbfbfd;
  --plate: #111119;
  --ink: #f6f6fb;
  --muted: #9b9bb0;
  --tilt-max: 8deg;
  --radius: 1.35rem;
  --sans: "Archivo",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: radial-gradient(40rem 24rem at 50% 45%, #1b1b28, transparent 72%), var(--bg);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}

@media (prefers-color-scheme: light) {
  .cb-29:not([data-theme="dark"]) {
    --bg: #e9e9ef;
  }
}

.cb-29[data-theme="light"] {
  --bg: #e9e9ef;
}

.cb-29,
.cb-29 *,
.cb-29 *::before,
.cb-29 *::after {
  box-sizing: border-box;
}

.cb-29__stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(1.5rem,6vw,4rem);
}

.cb-29__btn {
  --mx: 50%;
  --my: 50%;
  --tx: 0;
  --ty: 0;
  --tilt: 0deg;
  position: relative;
  overflow: hidden;
  isolation: isolate;
  cursor: pointer;
  padding: .5rem;
  border: .32rem solid var(--cut);
  border-radius: var(--radius);
  background: var(--plate);
  color: var(--ink);
  filter: drop-shadow(0 16px 26px rgba(0,0,0,.55));
  transform: perspective(700px) rotate3d(var(--ty),var(--tx),0,var(--tilt));
  transition: transform .35s cubic-bezier(.16,1,.3,1), filter .35s ease;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.cb-29__holo {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: .55;
  mix-blend-mode: color-dodge;
  background-image: repeating-linear-gradient(102deg, #ff5f6d 0 12%, #ffc371 18%, #47e5bc 30%, #4facfe 42%, #b06ab3 54%, #ff5f6d 66%), conic-gradient(from 210deg at var(--mx) var(--my), rgba(255,255,255,.55), transparent 28%, rgba(255,255,255,.4) 62%, transparent 88%);
  background-size: 340% 340%, 100% 100%;
  background-position: var(--mx) var(--my), 0 0;
  transition: opacity .35s ease;
}

.cb-29__plate {
  position: relative;
  z-index: 1;
  display: grid;
  gap: .15rem;
  justify-items: start;
  min-height: 3.1rem;
  padding: .7rem 1.5rem;
  border-radius: calc(var(--radius) - .5rem);
  background: linear-gradient(180deg, color-mix(in oklab,var(--plate) 80%,white 5%), var(--plate));
}

.cb-29__label {
  font-size: 1.0625rem;
  font-weight: 800;
  letter-spacing: -.02em;
}

.cb-29__sub {
  font-size: .6875rem;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.cb-29__peel {
  position: absolute;
  z-index: 3;
  top: -.05rem;
  right: -.05rem;
  width: 1.6rem;
  height: 1.6rem;
  pointer-events: none;
  background: linear-gradient(225deg, #ffffff, #cfd2dc 55%, #9aa0b0);
  clip-path: polygon(100% 0,100% 100%,0 0);
  filter: drop-shadow(-2px 3px 3px rgba(0,0,0,.4));
  scale: 0;
  transform-origin: 100% 0;
  transition: scale .4s cubic-bezier(.16,1,.3,1);
}

.cb-29__btn:is(:hover,:focus-visible) .cb-29__holo {
  opacity: .95;
}

.cb-29__btn:is(:hover,:focus-visible) .cb-29__peel {
  scale: 1;
}

.cb-29__btn:focus-visible {
  outline: 2px solid #4facfe;
  outline-offset: 5px;
}

.cb-29__btn:active {
  scale: .98;
}

@media (prefers-reduced-motion: reduce) {
  .cb-29__btn,
    .cb-29__holo,
    .cb-29__peel {
    transition: none;
  }

  .cb-29__btn {
    transform: none;
  }
}

@media (forced-colors: active) {
  .cb-29__btn {
    background: ButtonFace;
    border-color: ButtonText;
    filter: none;
  }

  .cb-29__holo,
    .cb-29__peel {
    display: none;
  }

  .cb-29__plate {
    background: ButtonFace;
    color: ButtonText;
  }
}
(() => {
  const btn = document.querySelector('.cb-29__btn');
  if (!btn) return;
  const max = 8;
  let box = null;
  btn.addEventListener('pointerenter', () => { box = btn.getBoundingClientRect(); });
  btn.addEventListener('pointermove', (e) => {
    if (e.pointerType !== 'mouse') return;
    box = box || btn.getBoundingClientRect();
    const nx = (e.clientX - box.left) / box.width;
    const ny = (e.clientY - box.top) / box.height;
    btn.style.setProperty('--mx', (nx * 100).toFixed(1) + '%');
    btn.style.setProperty('--my', (ny * 100).toFixed(1) + '%');
    btn.style.setProperty('--tx', (nx - 0.5).toFixed(3));
    btn.style.setProperty('--ty', (0.5 - ny).toFixed(3));
    btn.style.setProperty('--tilt', (Math.hypot(nx - 0.5, ny - 0.5) * 2 * max).toFixed(2) + 'deg');
  });
  btn.addEventListener('pointerleave', () => {
    box = null;
    btn.style.setProperty('--tilt', '0deg');
    btn.style.setProperty('--mx', '50%');
    btn.style.setProperty('--my', '50%');
  });
})();
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 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 Holographic Foil Sticker Button
Source: https://codefronts.com/components/css-buttons/css-holographic-foil-sticker-button/

A die-cut vinyl sticker whose holographic laminate shifts with the pointer: the rainbow sweep, the specular sheen and a 3D tilt all read from two pointer variables, and the corner peels up as you approach — the effect trading-card collectors call "cracked ice".
## HTML
```html
<div class="cb-29">
  <div class="cb-29__stage">
    <button class="cb-29__btn" type="button">
      <span class="cb-29__holo" aria-hidden="true"></span>
      <span class="cb-29__peel" aria-hidden="true"></span>
      <span class="cb-29__plate">
        <span class="cb-29__label">Limited drop</span>
        <span class="cb-29__sub">01 / 250</span>
      </span>
    </button>
  </div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Archivo:wght@600;700;800&display=swap');

.cb-29 {
  --bg: #0a0a0f;
  --cut: #fbfbfd;
  --plate: #111119;
  --ink: #f6f6fb;
  --muted: #9b9bb0;
  --tilt-max: 8deg;
  --radius: 1.35rem;
  --sans: "Archivo",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: radial-gradient(40rem 24rem at 50% 45%, #1b1b28, transparent 72%), var(--bg);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}

@media (prefers-color-scheme: light) {
  .cb-29:not([data-theme="dark"]) {
    --bg: #e9e9ef;
  }
}

.cb-29[data-theme="light"] {
  --bg: #e9e9ef;
}

.cb-29,
.cb-29 *,
.cb-29 *::before,
.cb-29 *::after {
  box-sizing: border-box;
}

.cb-29__stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(1.5rem,6vw,4rem);
}

.cb-29__btn {
  --mx: 50%;
  --my: 50%;
  --tx: 0;
  --ty: 0;
  --tilt: 0deg;
  position: relative;
  overflow: hidden;
  isolation: isolate;
  cursor: pointer;
  padding: .5rem;
  border: .32rem solid var(--cut);
  border-radius: var(--radius);
  background: var(--plate);
  color: var(--ink);
  filter: drop-shadow(0 16px 26px rgba(0,0,0,.55));
  transform: perspective(700px) rotate3d(var(--ty),var(--tx),0,var(--tilt));
  transition: transform .35s cubic-bezier(.16,1,.3,1), filter .35s ease;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.cb-29__holo {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: .55;
  mix-blend-mode: color-dodge;
  background-image: repeating-linear-gradient(102deg, #ff5f6d 0 12%, #ffc371 18%, #47e5bc 30%, #4facfe 42%, #b06ab3 54%, #ff5f6d 66%), conic-gradient(from 210deg at var(--mx) var(--my), rgba(255,255,255,.55), transparent 28%, rgba(255,255,255,.4) 62%, transparent 88%);
  background-size: 340% 340%, 100% 100%;
  background-position: var(--mx) var(--my), 0 0;
  transition: opacity .35s ease;
}

.cb-29__plate {
  position: relative;
  z-index: 1;
  display: grid;
  gap: .15rem;
  justify-items: start;
  min-height: 3.1rem;
  padding: .7rem 1.5rem;
  border-radius: calc(var(--radius) - .5rem);
  background: linear-gradient(180deg, color-mix(in oklab,var(--plate) 80%,white 5%), var(--plate));
}

.cb-29__label {
  font-size: 1.0625rem;
  font-weight: 800;
  letter-spacing: -.02em;
}

.cb-29__sub {
  font-size: .6875rem;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.cb-29__peel {
  position: absolute;
  z-index: 3;
  top: -.05rem;
  right: -.05rem;
  width: 1.6rem;
  height: 1.6rem;
  pointer-events: none;
  background: linear-gradient(225deg, #ffffff, #cfd2dc 55%, #9aa0b0);
  clip-path: polygon(100% 0,100% 100%,0 0);
  filter: drop-shadow(-2px 3px 3px rgba(0,0,0,.4));
  scale: 0;
  transform-origin: 100% 0;
  transition: scale .4s cubic-bezier(.16,1,.3,1);
}

.cb-29__btn:is(:hover,:focus-visible) .cb-29__holo {
  opacity: .95;
}

.cb-29__btn:is(:hover,:focus-visible) .cb-29__peel {
  scale: 1;
}

.cb-29__btn:focus-visible {
  outline: 2px solid #4facfe;
  outline-offset: 5px;
}

.cb-29__btn:active {
  scale: .98;
}

@media (prefers-reduced-motion: reduce) {
  .cb-29__btn,
    .cb-29__holo,
    .cb-29__peel {
    transition: none;
  }

  .cb-29__btn {
    transform: none;
  }
}

@media (forced-colors: active) {
  .cb-29__btn {
    background: ButtonFace;
    border-color: ButtonText;
    filter: none;
  }

  .cb-29__holo,
    .cb-29__peel {
    display: none;
  }

  .cb-29__plate {
    background: ButtonFace;
    color: ButtonText;
  }
}
```

## JavaScript
```js
(() => {
  const btn = document.querySelector('.cb-29__btn');
  if (!btn) return;
  const max = 8;
  let box = null;
  btn.addEventListener('pointerenter', () => { box = btn.getBoundingClientRect(); });
  btn.addEventListener('pointermove', (e) => {
    if (e.pointerType !== 'mouse') return;
    box = box || btn.getBoundingClientRect();
    const nx = (e.clientX - box.left) / box.width;
    const ny = (e.clientY - box.top) / box.height;
    btn.style.setProperty('--mx', (nx * 100).toFixed(1) + '%');
    btn.style.setProperty('--my', (ny * 100).toFixed(1) + '%');
    btn.style.setProperty('--tx', (nx - 0.5).toFixed(3));
    btn.style.setProperty('--ty', (0.5 - ny).toFixed(3));
    btn.style.setProperty('--tilt', (Math.hypot(nx - 0.5, ny - 0.5) * 2 * max).toFixed(2) + 'deg');
  });
  btn.addEventListener('pointerleave', () => {
    box = null;
    btn.style.setProperty('--tilt', '0deg');
    btn.style.setProperty('--mx', '50%');
    btn.style.setProperty('--my', '50%');
  });
})();
```

How this works

Two pointer variables drive three independent layers. The holo is a repeating linear rainbow whose position tracks the cursor, multiplied by a coarse conic ripple:

.cb-29__holo{
  background:
    repeating-linear-gradient(102deg,
      #ff5f6d 0 12%, #ffc371 18%, #47e5bc 30%, #4facfe 42%, #b06ab3 54%, #ff5f6d 66%),
    conic-gradient(from 210deg at var(--mx) var(--my),
      rgba(255,255,255,.5), transparent 30%, rgba(255,255,255,.35) 62%, transparent);
  background-size:340% 340%, 100% 100%;
  background-position:var(--mx) var(--my), 0 0;
  mix-blend-mode:color-dodge;
}

The tilt is a single rotate3d fed by the normalised offsets, capped at 9° so the sticker never looks detached:

transform:perspective(700px) rotate3d(var(--ty),var(--tx),0,var(--tilt));

The die-cut is a thick white border plus a drop-shadow filter (not box-shadow) so the shadow follows the cut shape, and the peel is a small triangle whose clip-path grows on hover with a soft shadow behind it.

Make it yours

  • Change the foil by editing the rainbow stops — two-tone (cyan/magenta) reads as modern, five-tone as retro.
  • --tilt-max caps the rotation; 6-9° is convincing, 20° looks like a card flip.
  • Swap mix-blend-mode from color-dodge to overlay for a subtler laminate on light backgrounds.
  • Remove the peel triangle for a flush sticker, or mirror it to the opposite corner.

Gotchas — read before shipping

  • mix-blend-mode needs isolation:isolate on the button, or the foil blends with the page background and turns to mud.
  • Tilt on pointermove must be pointer-only; a keyboard user gets a static sticker plus a focus ring (wired here).
  • Reading getBoundingClientRect() on every move is fine for one sticker — cache it per pointerenter if you build a wall of them.
  • Holographic gradients rarely pass contrast behind text; the label here sits on a dark plate with its own solid background.
  • filter:drop-shadow() is significantly more expensive than box-shadow — keep it on one element and never animate its blur.

Browser support

ChromeSafariFirefoxEdge
111+16.2+113+111+

mix-blend-mode, conic-gradient and drop-shadow are baseline. color-mix() (Chrome 111 / Safari 16.2 / Firefox 113) falls back to hex tokens; the tilt uses a transform string for maximum compatibility.

Techniques used in this demo

Search CodeFronts

Loading…