Pure CSS Products & branding #hover #product #music #album

Vinyl sleeve in pure CSS

The record is sandwiched between the two sides of the sleeve. On hover or focus it slides out, its label starts to spin and the sleeve turns its open end to you.

  • 125 lines of CSS
  • 10 lines of HTML
  • No JavaScript
  • No dependencies
  • Snippet under MIT
GitHub

How it works

  1. Every length is a multiple of one base unit, --u, tied to the canvas, so the sleeve, its type and the record's grooves are the same share of a gallery card, the editor and a recording canvas. The numbers below are those units.
  2. The sleeve is a real sandwich in Z: back board at translateZ(-3 units), record at 0, front board at +3. The record hides behind the front board with no z-index at all.
  3. Sliding out is one transition on the record: translateX(66 units). The whole set turns at the same time, so the open end swings toward you.
  4. The label spins with an animation that starts paused; :hover only flips animation-play-state to running, so it stops where it is instead of snapping back.
  5. The grooves are a repeating-radial-gradient; the reflections are a conic-gradient on the same element that does not spin, like light on a real record.

Key techniques

  • three planes at different translateZ
  • animation-play-state toggled by :hover
  • conic + repeating-radial gradients
  • static hit area

Copy-paste code

HTML 10 lines

<div class="scene">
  <div class="vinyl" tabindex="0">
    <div class="set">
      <i class="back"></i>
      <i class="spine"></i>
      <div class="disc"><i class="label"></i></div>
      <div class="front"><b>NIGHT<br>DRIVE</b><span>Side A · 33 1/3</span></div>
    </div>
  </div>
</div>

CSS 125 lines

.scene {
  /* one base unit: every length below is a multiple of it, so the sleeve is the same share of a
     card, the editor, a full screen and a recording canvas */
  --u: 0.48vmin;
  perspective: calc(800 * var(--u));
}

/* static hit area */
.vinyl {
  display: grid;
  place-items: center;
  width: calc(216 * var(--u));
  height: calc(160 * var(--u));
  border-radius: calc(16 * var(--u));
  outline-offset: calc(-4 * var(--u));
  cursor: pointer;
  transform-style: preserve-3d;
}

.set {
  position: relative;
  width: calc(120 * var(--u));
  height: calc(120 * var(--u));
  pointer-events: none;
  transform-style: preserve-3d;
  transform: translateX(calc(-8 * var(--u))) rotateX(4deg) rotateY(18deg);
  transition: transform 0.7s cubic-bezier(0.3, 1.2, 0.5, 1);
}

.front,
.back {
  position: absolute;
  inset: 0;
  border-radius: calc(3 * var(--u));
}

.back {
  background: #312a5d;
  transform: translateZ(calc(-3 * var(--u)));
}

.front {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  box-sizing: border-box;
  padding: calc(10 * var(--u));
  overflow: hidden;
  border: calc(1 * var(--u)) solid rgb(255 255 255 / 0.22);
  background:
    radial-gradient(circle at 78% 72%, transparent 0 calc(17 * var(--u)), rgb(255 255 255 / 0.9) calc(17.5 * var(--u)) calc(19 * var(--u)), transparent calc(19.5 * var(--u)) calc(27 * var(--u)), rgb(255 255 255 / 0.55) calc(27.5 * var(--u)) calc(29 * var(--u)), transparent calc(29.5 * var(--u)) calc(38 * var(--u)), rgb(255 255 255 / 0.3) calc(38.5 * var(--u)) calc(40 * var(--u)), transparent calc(40.5 * var(--u))),
    radial-gradient(circle at 78% 72%, #ffb547 0 calc(17 * var(--u)), transparent calc(17.5 * var(--u))),
    linear-gradient(150deg, #6a45f5, #d1206f 85%); /* deep enough for the white words */
  color: #fff;
  font-family: Inter, system-ui, sans-serif;
  backface-visibility: hidden;
  transform: translateZ(calc(3 * var(--u)));
}

.front b {
  font-size: calc(17 * var(--u));
  font-weight: 900;
  line-height: 0.95;
  letter-spacing: calc(-0.5 * var(--u));
}

.front span {
  font-size: calc(8 * var(--u));
  font-weight: 700;
  letter-spacing: calc(1 * var(--u));
  text-transform: uppercase; /* full white: at 0.85 on the sleeve it was 2.9:1 */
}

/* closes the left side: centred on the left edge, turned side-on */
.spine {
  position: absolute;
  top: 0;
  left: calc(-3 * var(--u));
  width: calc(6 * var(--u));
  height: 100%;
  background: #5846a3;
  transform: rotateY(90deg);
}

.disc {
  position: absolute;
  inset: calc(6 * var(--u));
  display: grid;
  place-items: center;
  border-radius: 50%;
  background:
    conic-gradient(from 35deg, transparent 0 6%, rgb(255 255 255 / 0.22) 11%, transparent 17% 56%, rgb(255 255 255 / 0.16) 61%, transparent 67%),
    repeating-radial-gradient(circle, #0c0c12 0 calc(1.5 * var(--u)), #1f1f2a calc(1.5 * var(--u)) calc(3 * var(--u)));
  transform: translateX(calc(12 * var(--u)));
  transition: transform 0.7s cubic-bezier(0.3, 1.1, 0.5, 1);
}

.label {
  width: calc(40 * var(--u));
  height: calc(40 * var(--u));
  border-radius: 50%;
  background:
    radial-gradient(circle, #0c0c12 0 calc(2.5 * var(--u)), transparent calc(3 * var(--u))),
    conic-gradient(#ffb547 0 25%, #ff4d9d 0 50%, #ffb547 0 75%, #ff4d9d 0);
  animation: spin 1.8s linear infinite paused;
}

.vinyl:hover .set,
.vinyl:focus-visible .set {
  transform: translateX(calc(-36 * var(--u))) rotateX(6deg) rotateY(-22deg);
}

.vinyl:hover .disc,
.vinyl:focus-visible .disc {
  transform: translateX(calc(66 * var(--u)));
}

.vinyl:hover .label,
.vinyl:focus-visible .label {
  animation-play-state: running;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}
Standalone snippet · plain CSS, no build step, no dependencies.