Pure CSS Products & branding #hover #product #card #flip #branding

Business card in pure CSS

A thick business card with painted edges. Hover or focus and it flips to show the back; the logo and the text are lifted off the paper with translateZ, so they shift against it as it turns.

  • 195 lines of CSS
  • 20 lines of HTML
  • No JavaScript
  • No dependencies
  • MIT licensed

How it works

  1. The card has real thickness: the printed front and back are two planes at translateZ(±2px), the back turned with rotateY(180deg). Both hide their backface, so each shows only while it faces you.
  2. The painted edge is four rounded slabs 1px apart (they fill the rounded corners) plus four flat walls on the straight parts, which give the edge a clean, lit surface when the card is edge-on.
  3. Embossing is real depth: each printed side is itself preserve-3d, and the logo and the lines of text are lifted off it with translateZ(2–6px). As the card turns, they slide against the paper.
  4. The idle sway and the flip are on two different elements, so they never fight over transform. The flip is a plain transition with a small overshoot.
  5. The hovered element is a static wrapper; the moving card inside has pointer-events: none, so the flip cannot pull the card out from under the pointer.

Key techniques

  • two faces at ±depth/2, back turned 180deg
  • rounded slabs + walls for the edge
  • embossing = children lifted with translateZ
  • flip and idle sway on separate elements

Copy-paste code

HTML 20 lines

<div class="scene">
  <div class="bizcard" tabindex="0">
    <div class="float">
      <i class="shadow"></i>
      <div class="card">
        <i class="slab" style="--i:0"></i><i class="slab" style="--i:1"></i><i class="slab" style="--i:2"></i><i class="slab" style="--i:3"></i>
        <i class="wall wall-t"></i><i class="wall wall-b"></i><i class="wall wall-l"></i><i class="wall wall-r"></i>
        <div class="front">
          <b class="logo"><i></i><i></i><i></i></b>
          <strong>PRISM</strong><small>DESIGN STUDIO</small>
        </div>
        <div class="back">
          <b></b>
          <strong>Alex Morgan</strong><small>Creative director</small>
          <span>alex@prism.studio</span><span>+1 555 0142</span><span>prism.studio</span>
        </div>
      </div>
    </div>
  </div>
</div>

CSS 195 lines

.scene {
  perspective: 800px;
}

/* the static hit area */
.bizcard {
  --ink: #0f1020;
  --edge: #ff4d9d;
  --foil: #ffd291;
  --foil-deep: #aa6c16;
  display: grid;
  place-items: center;
  width: 220px;
  height: 170px;
  border-radius: 16px;
  cursor: pointer;
  font-family: system-ui, sans-serif;
  transform-style: preserve-3d;
}

/* the tilt and a slow idle sway */
.float {
  position: relative;
  width: 176px;
  height: 100px;
  pointer-events: none;
  transform-style: preserve-3d;
  animation: float 6s ease-in-out infinite alternate;
}

.shadow {
  position: absolute;
  inset: 10px -6px -16px;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgb(0 0 0 / 0.38), transparent);
  transform: translateZ(-36px);
}

/* the flip, on its own element */
.card {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  transition: transform 0.9s cubic-bezier(0.35, 1.25, 0.45, 1);
}

.bizcard:hover .card,
.bizcard:focus-visible .card {
  transform: rotateY(180deg);
}

/* 4px thick: four rounded slabs fill the corners of the edge... */
.slab {
  position: absolute;
  inset: 0.5px;
  border-radius: 6px;
  background: var(--edge);
  transform: translateZ(calc((var(--i) - 1.5) * 1px));
}

/* ...four walls give the straight parts a lit surface (inset by the 6px radius) */
.wall {
  position: absolute;
  background: linear-gradient(90deg, #b3366e, var(--edge) 50%, #ff82ba);
}

.wall-t, .wall-b { left: 6px; top: calc(50% - 2px); width: 164px; height: 4px; }
.wall-l, .wall-r { top: 6px; left: calc(50% - 2px); width: 4px; height: 88px; }
.wall-t { transform: rotateX(90deg) translateZ(50px); }
.wall-b { transform: rotateX(-90deg) translateZ(50px); }
.wall-l { transform: rotateY(-90deg) translateZ(88px); }
.wall-r { transform: rotateY(90deg) translateZ(88px); }

/* each printed side is 3D itself, so its print can be lifted off it */
.front,
.back {
  position: absolute;
  inset: 0;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  border-radius: 6px;
  transform-style: preserve-3d;
  backface-visibility: hidden;
}

.front > *,
.back > * {
  backface-visibility: hidden;
}

.front {
  align-items: center;
  justify-content: center;
  gap: 3px;
  background:
    radial-gradient(circle at 50% 38%, rgb(139 108 255 / 0.38), transparent 55%),
    repeating-linear-gradient(135deg, rgb(255 255 255 / 0.035) 0 1px, transparent 1px 6px),
    var(--ink);
  transform: translateZ(2px);
}

.front strong {
  margin-top: 6px;
  color: var(--foil);
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 5px;
  text-indent: 5px; /* balances the space after the last letter */
  text-shadow: 0 1px 0 var(--foil-deep);
  transform: translateZ(3px);
}

.front small {
  color: rgb(255 255 255 / 0.6);
  font-size: 5.5px;
  font-weight: 600;
  letter-spacing: 2.5px;
  text-indent: 2.5px;
  transform: translateZ(1.5px);
}

/* foil prism logo, lifted the most */
.logo {
  position: relative;
  width: 34px;
  height: 30px;
  transform-style: preserve-3d;
  transform: translateZ(6px);
}

.logo i {
  position: absolute;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}

.logo i:nth-child(1) { inset: 0; background: linear-gradient(135deg, #fff6dc, var(--foil) 40%, var(--foil-deep)); }
.logo i:nth-child(2) { inset: 9px 7.5px 3px; background: var(--ink); }
.logo i:nth-child(3) {
  top: 15px;
  left: 30px;
  width: 26px;
  height: 9px;
  clip-path: polygon(0 40%, 100% 0, 100% 100%, 0 60%); /* the spectrum fanning out */
  background: linear-gradient(#ff4d9d 0 33%, #ffb547 0 66%, #2ee6d6 0);
}

.back {
  justify-content: center;
  padding: 12px 14px;
  color: #fff;
  background:
    radial-gradient(circle at 100% 0%, rgb(255 181 71 / 0.55), transparent 50%),
    linear-gradient(135deg, #8b6cff, #c55dcb);
  transform: rotateY(180deg) translateZ(2px);
}

.back strong { font-size: 13px; font-weight: 800; line-height: 1; text-shadow: 0 1px 0 rgb(0 0 0 / 0.25); transform: translateZ(3px); }
.back small { margin: 3px 0 9px; font-size: 6.5px; font-weight: 600; letter-spacing: 1.5px; text-transform: uppercase; opacity: 0.85; transform: translateZ(2px); }

.back span {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-top: 3px;
  font-size: 6.5px;
  font-weight: 500;
  transform: translateZ(1.5px);
}

.back span::before {
  content: '';
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 1.5px rgb(255 77 157 / 0.6);
}

/* the small mark in the corner */
.back b {
  position: absolute;
  top: 12px;
  right: 14px;
  width: 18px;
  height: 16px;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
  background: linear-gradient(135deg, #fff, rgb(255 255 255 / 0.55));
  transform: translateZ(4px);
}

@keyframes float {
  from { transform: translateY(-3px) rotateX(16deg) rotateY(-20deg) rotateZ(-5deg); }
  to   { transform: translateY(3px) rotateX(10deg) rotateY(-8deg) rotateZ(-3deg); }
}

Sass source 250 lines

$w: 176px;
$h: 100px;
$d: 4px; // a thick, premium card stock
$r: 6px; // corner radius

// Static hit area: hovering it never moves it.
.d-businesscard {
  --ink: #0f1020;
  --edge: var(--hot);
  --foil: color-mix(in srgb, var(--warm) 80%, #fff);
  --foil-deep: color-mix(in srgb, var(--warm) 60%, #3a2200);
  display: grid;
  place-items: center;
  width: 220px;
  height: 170px;
  border-radius: 16px;
  outline-offset: -4px;
  cursor: pointer;
  transform-style: preserve-3d;

  // the tilt, with a slow idle sway so it is never quite still
  &__float {
    position: relative;
    width: $w;
    height: $h;
    pointer-events: none;
    transform-style: preserve-3d;
    animation: d-businesscard-float 6s ease-in-out infinite alternate;
  }

  &__shadow {
    position: absolute;
    inset: 10px -6px -16px;
    border-radius: 50%;
    background: radial-gradient(closest-side, rgb(0 0 0 / 0.38), transparent);
    transform: translateZ(-36px);
  }

  // the flip lives on its own element, inside the sway: the two never fight over transform
  &__card {
    position: absolute;
    inset: 0;
    transform-style: preserve-3d;
    transition: transform 0.9s cubic-bezier(0.35, 1.25, 0.45, 1);
  }

  // Four rounded slabs, 1px apart, fill the rounded corners of the edge...
  &__slab {
    position: absolute;
    inset: 0.5px;
    border-radius: $r;
    background: var(--edge);
    transform: translateZ(calc((var(--i) - 1.5) * 1px));
  }

  // ...and four flat walls give the straight part of the edge a clean, lit surface.
  &__wall {
    position: absolute;
    background: linear-gradient(90deg, color-mix(in srgb, var(--edge) 70%, #000), var(--edge) 50%, color-mix(in srgb, var(--edge) 70%, #fff));

    &--t,
    &--b {
      left: $r;
      top: calc(50% - #{$d * 0.5});
      width: $w - 2 * $r;
      height: $d;
    }

    &--l,
    &--r {
      top: $r;
      left: calc(50% - #{$d * 0.5});
      width: $d;
      height: $h - 2 * $r;
    }

    &--t {
      transform: rotateX(90deg) translateZ($h * 0.5);
    }

    &--b {
      transform: rotateX(-90deg) translateZ($h * 0.5);
    }

    &--l {
      transform: rotateY(-90deg) translateZ($w * 0.5);
    }

    &--r {
      transform: rotateY(90deg) translateZ($w * 0.5);
    }
  }

  // --- the two printed sides ------------------------------------------------------------
  // Each side is a preserve-3d plane, so what is printed on it can be lifted off it with
  // translateZ: the embossing. Every piece hides its own backface (it is 3D, not flat paint).
  &__front,
  &__back {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    border-radius: $r;
    transform-style: preserve-3d;
    backface-visibility: hidden;

    > * {
      backface-visibility: hidden;
    }
  }

  &__front {
    align-items: center;
    justify-content: center;
    gap: 3px;
    background:
      radial-gradient(circle at 50% 38%, color-mix(in srgb, var(--accent) 38%, transparent), transparent 55%),
      repeating-linear-gradient(135deg, rgb(255 255 255 / 0.035) 0 1px, transparent 1px 6px),
      var(--ink);
    transform: translateZ($d * 0.5);

    strong {
      margin-top: 6px;
      color: var(--foil);
      font-size: 13px;
      font-weight: 800;
      letter-spacing: 5px;
      text-indent: 5px; // balances the letter-spacing after the last letter
      text-shadow: 0 1px 0 var(--foil-deep);
      transform: translateZ(3px);
    }

    small {
      color: rgb(255 255 255 / 0.6);
      font-size: 5.5px;
      font-weight: 600;
      letter-spacing: 2.5px;
      text-indent: 2.5px;
      transform: translateZ(1.5px);
    }
  }

  // foil prism: a gold triangle with a dark core, a light ray in and a spectrum out
  &__logo {
    position: relative;
    width: 34px;
    height: 30px;
    transform-style: preserve-3d;
    transform: translateZ(6px);

    i {
      position: absolute;
      clip-path: polygon(50% 0, 100% 100%, 0 100%);
    }

    i:nth-child(1) {
      inset: 0;
      background: linear-gradient(135deg, #fff6dc, var(--foil) 40%, var(--foil-deep));
    }

    i:nth-child(2) {
      inset: 9px 7.5px 3px;
      background: var(--ink);
    }

    i:nth-child(3) {
      top: 15px;
      left: 30px;
      width: 26px;
      height: 9px;
      clip-path: polygon(0 40%, 100% 0, 100% 100%, 0 60%);
      background: linear-gradient(var(--hot) 0 33%, var(--warm) 0 66%, var(--accent-2) 0);
    }
  }

  &__back {
    justify-content: center;
    padding: 12px 14px;
    color: #fff;
    background:
      radial-gradient(circle at 100% 0%, color-mix(in srgb, var(--warm) 55%, transparent), transparent 50%),
      linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 45%, var(--hot)));
    transform: rotateY(180deg) translateZ($d * 0.5);

    strong {
      font-size: 13px;
      font-weight: 800;
      line-height: 1;
      text-shadow: 0 1px 0 rgb(0 0 0 / 0.25);
      transform: translateZ(3px);
    }

    small {
      margin: 3px 0 9px;
      font-size: 6.5px;
      font-weight: 600;
      letter-spacing: 1.5px;
      text-transform: uppercase;
      opacity: 0.85;
      transform: translateZ(2px);
    }

    span {
      display: flex;
      align-items: center;
      gap: 5px;
      margin-top: 3px;
      font-size: 6.5px;
      font-weight: 500;
      transform: translateZ(1.5px);

      // a little round bullet in the edge colour
      &::before {
        content: '';
        width: 4px;
        height: 4px;
        border-radius: 50%;
        background: #fff;
        box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--hot) 60%, transparent);
      }
    }

    // the small mark in the corner
    b {
      position: absolute;
      top: 12px;
      right: 14px;
      width: 18px;
      height: 16px;
      clip-path: polygon(50% 0, 100% 100%, 0 100%);
      background: linear-gradient(135deg, #fff, rgb(255 255 255 / 0.55));
      transform: translateZ(4px);
    }
  }

  &:hover &__card,
  &:focus-visible &__card {
    transform: rotateY(180deg);
  }
}

@keyframes d-businesscard-float {
  from {
    transform: translateY(-3px) rotateX(16deg) rotateY(-20deg) rotateZ(-5deg);
  }

  to {
    transform: translateY(3px) rotateX(10deg) rotateY(-8deg) rotateZ(-3deg);
  }
}
Standalone snippet · plain CSS, no build step, no dependencies.