Pure CSS Products & branding #loop #product #mockup #device

Phone mockup in pure CSS

A phone with real thickness: screen, back, four thin walls and a stack of rounded slabs that fills the corners. It turns to show both sides.

  • 166 lines of CSS
  • 18 lines of HTML
  • No JavaScript
  • No dependencies
  • MIT licensed

How it works

  1. The screen and the back are two panels at translateZ(±depth / 2). The back is turned with rotateY(180deg) and both hide their backface, so each is only drawn while it faces you.
  2. Four thin walls close the sides. Each one only covers the straight part of its edge: it is inset by the corner radius, because a flat plane cannot follow a rounded corner.
  3. The corners are filled by nine rounded slabs, 1px apart, stacked through the body. From any angle their edges overlap into a solid rounded rim.
  4. The turn uses alternate with ease-in-out: it lingers on the front view and on the back view, and it is seamless without matching start and end.

Key techniques

  • front / back at ±depth/2
  • walls inset by the corner radius
  • rounded slabs plug the corners
  • alternate keyframes

Copy-paste code

HTML 18 lines

<div class="scene">
  <div class="phone">
    <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="slab" style="--i:4"></i><i class="slab" style="--i:5"></i>
    <i class="slab" style="--i:6"></i><i class="slab" style="--i:7"></i><i class="slab" style="--i:8"></i>
    <i class="wall wall-l"></i><i class="wall wall-r"></i>
    <i class="wall wall-t"></i><i class="wall wall-b"></i>
    <div class="front">
      <div class="screen">
        <span class="notch"></span>
        <strong>9:41</strong><small>Friday 18</small>
        <div class="widget"><i></i><i></i><i></i></div>
        <div class="dock"><i></i><i></i><i></i></div>
      </div>
    </div>
    <div class="back"><i></i><b></b></div>
  </div>
</div>

CSS 166 lines

.scene {
  perspective: 800px;
}

/* 84 × 168, 10px thick, 15px corner radius */
.phone {
  --metal: #5a607f;
  --shine: color-mix(in srgb, #eceefb 60%, var(--metal));
  position: relative;
  width: 84px;
  height: 168px;
  transform-style: preserve-3d;
  animation: turn 7s ease-in-out infinite alternate;
}

/* nine rounded slabs, 1px apart, fill the body so the corners look solid */
.slab {
  position: absolute;
  inset: 0.5px;
  border-radius: 15px;
  background: var(--metal);
  transform: translateZ(calc((var(--i) - 4) * 1px));
}

/* walls cover only the straight part of each edge: inset by the radius */
.wall {
  position: absolute;
  background: linear-gradient(90deg, var(--metal), var(--shine) 50%, var(--metal));
}

.wall-l,
.wall-r {
  left: calc(50% - 5px);
  top: 15px;
  width: 10px;
  height: 138px;
}

.wall-t,
.wall-b {
  left: 15px;
  top: calc(50% - 5px);
  width: 54px;
  height: 10px;
  background: linear-gradient(var(--metal), var(--shine) 50%, var(--metal));
}

.wall-l { transform: rotateY(-90deg) translateZ(42px); } /* half the width */
.wall-r { transform: rotateY(90deg) translateZ(42px); }
.wall-t { transform: rotateX(90deg) translateZ(84px); }  /* half the height */
.wall-b { transform: rotateX(-90deg) translateZ(84px); }

.front,
.back {
  position: absolute;
  inset: 0;
  border-radius: 15px;
  backface-visibility: hidden;
}

.front {
  padding: 4px; /* the bezel */
  background: #05060c;
  transform: translateZ(5px); /* half the thickness */
}

.screen {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  box-sizing: border-box;
  height: 100%;
  padding: 18px 7px 7px;
  overflow: hidden;
  border-radius: 11px;
  background:
    radial-gradient(circle at 80% 8%, rgb(255 181 71 / 0.8), transparent 45%),
    linear-gradient(165deg, #8b6cff, #ff4d9d 70%, #ffb547 120%);
  color: #fff;
}

.screen strong { font-size: 21px; font-weight: 800; line-height: 1; letter-spacing: -0.5px; }
.screen small { margin-top: 2px; font-size: 7px; opacity: 0.85; }

.notch {
  position: absolute;
  top: 5px;
  left: calc(50% - 12px);
  width: 24px;
  height: 6px;
  border-radius: 3px;
  background: #05060c;
}

.widget {
  display: grid;
  gap: 4px;
  box-sizing: border-box;
  width: 100%;
  margin-top: 12px;
  padding: 7px;
  border: 1px solid rgb(255 255 255 / 0.35);
  border-radius: 8px;
  background: rgb(255 255 255 / 0.18);
}

.widget i { height: 4px; border-radius: 2px; background: rgb(255 255 255 / 0.85); }
.widget i:nth-child(2) { width: 70%; opacity: 0.6; }
.widget i:nth-child(3) { width: 45%; opacity: 0.6; }

.dock {
  display: flex;
  gap: 5px;
  justify-content: center;
  box-sizing: border-box;
  width: 100%;
  margin-top: auto;
  padding: 5px;
  border-radius: 9px;
  background: rgb(255 255 255 / 0.2);
}

.dock i { width: 13px; height: 13px; border-radius: 4px; background: rgb(255 255 255 / 0.9); }
.dock i:nth-child(2) { background: #2ee6d6; }
.dock i:nth-child(3) { background: #05060c; }

.back {
  border: 1px solid rgb(236 238 251 / 0.25);
  background:
    linear-gradient(125deg, transparent 40%, rgb(255 255 255 / 0.14) 50%, transparent 60%),
    linear-gradient(160deg, #5546a2, #26254f);
  transform: rotateY(180deg) translateZ(5px);
}

/* camera island with two lenses */
.back i {
  position: absolute;
  top: 8px;
  left: 8px;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  background:
    radial-gradient(circle at 9px 9px, #4a5078 0 2px, #05060c 2.5px 6px, transparent 6.5px),
    radial-gradient(circle at 21px 21px, #4a5078 0 2px, #05060c 2.5px 6px, transparent 6.5px),
    #2f2957;
}

/* logo */
.back b {
  position: absolute;
  top: calc(50% - 8px);
  left: calc(50% - 8px);
  box-sizing: border-box;
  width: 16px;
  height: 16px;
  border: 3px solid rgb(255 255 255 / 0.55);
  border-radius: 50%;
}

/* 215° = 180° + 35°: the back, seen from the mirrored angle */
@keyframes turn {
  from { transform: rotateX(-8deg) rotateY(-35deg); }
  to   { transform: rotateX(-8deg) rotateY(215deg); }
}

Sass source 212 lines

$w: 84px;
$h: 168px;
$d: 10px; // thickness
$r: 15px; // corner radius

.d-phone {
  --metal: color-mix(in srgb, var(--muted) 55%, var(--surface-solid));
  position: relative;
  width: $w;
  height: $h;
  transform-style: preserve-3d;
  // -35° shows the screen, 215° (= 180° + 35°) shows the back from the mirrored angle.
  // `alternate` + ease-in-out lingers on both poses and is seamless by construction.
  animation: d-phone-turn 7s ease-in-out infinite alternate;

  // Flat walls cannot follow a rounded corner. Nine rounded slabs, 1px apart, fill the body
  // so the corners look solid from every angle.
  &__slab {
    position: absolute;
    inset: 0.5px;
    border-radius: $r;
    background: var(--metal);
    transform: translateZ(calc((var(--i) - 4) * 1px));
  }

  // The walls cover only the straight part of each edge (inset by the radius).
  &__wall {
    position: absolute;
    background: linear-gradient(90deg, var(--metal), color-mix(in srgb, var(--text) 60%, var(--metal)) 50%, var(--metal));

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

    &--t,
    &--b {
      left: $r;
      top: calc(50% - #{$d * 0.5});
      width: $w - 2 * $r;
      height: $d;
      background: linear-gradient(var(--metal), color-mix(in srgb, var(--text) 60%, var(--metal)) 50%, var(--metal));
    }

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

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

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

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

  &__front,
  &__back {
    position: absolute;
    inset: 0;
    border-radius: $r;
    backface-visibility: hidden;
  }

  &__front {
    padding: 4px; // the bezel
    background: #05060c;
    transform: translateZ($d * 0.5);
  }

  &__screen {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 100%;
    padding: 18px 7px 7px;
    overflow: hidden;
    border-radius: $r - 4px;
    background:
      radial-gradient(circle at 80% 8%, color-mix(in srgb, var(--warm) 80%, transparent), transparent 45%),
      linear-gradient(165deg, var(--accent), var(--hot) 70%, var(--warm) 120%);
    color: #fff;

    strong {
      font-size: 21px;
      font-weight: 800;
      line-height: 1;
      letter-spacing: -0.5px;
    }

    small {
      margin-top: 2px;
      font-size: 7px;
      opacity: 0.85;
    }
  }

  &__notch {
    position: absolute;
    top: 5px;
    left: calc(50% - 12px);
    width: 24px;
    height: 6px;
    border-radius: 3px;
    background: #05060c;
  }

  &__widget {
    display: grid;
    gap: 4px;
    width: 100%;
    margin-top: 12px;
    padding: 7px;
    border: 1px solid rgb(255 255 255 / 0.35);
    border-radius: 8px;
    background: rgb(255 255 255 / 0.18);

    i {
      height: 4px;
      border-radius: 2px;
      background: rgb(255 255 255 / 0.85);

      &:nth-child(2) {
        width: 70%;
        opacity: 0.6;
      }

      &:nth-child(3) {
        width: 45%;
        opacity: 0.6;
      }
    }
  }

  &__dock {
    display: flex;
    gap: 5px;
    justify-content: center;
    width: 100%;
    margin-top: auto;
    padding: 5px;
    border-radius: 9px;
    background: rgb(255 255 255 / 0.2);

    i {
      width: 13px;
      height: 13px;
      border-radius: 4px;
      background: rgb(255 255 255 / 0.9);

      &:nth-child(2) {
        background: var(--accent-2);
      }

      &:nth-child(3) {
        background: #05060c;
      }
    }
  }

  &__back {
    border: 1px solid color-mix(in srgb, var(--text) 25%, transparent);
    background:
      linear-gradient(125deg, transparent 40%, rgb(255 255 255 / 0.14) 50%, transparent 60%),
      linear-gradient(160deg, color-mix(in srgb, var(--accent) 55%, var(--surface-solid)), color-mix(in srgb, var(--accent) 15%, var(--surface-solid)));
    transform: rotateY(180deg) translateZ($d * 0.5);

    // camera island with two lenses
    i {
      position: absolute;
      top: 8px;
      left: 8px;
      width: 30px;
      height: 30px;
      border-radius: 9px;
      background:
        radial-gradient(circle at 9px 9px, #4a5078 0 2px, #05060c 2.5px 6px, transparent 6.5px),
        radial-gradient(circle at 21px 21px, #4a5078 0 2px, #05060c 2.5px 6px, transparent 6.5px),
        color-mix(in srgb, var(--accent) 25%, #10121f);
    }

    // logo
    b {
      position: absolute;
      top: calc(50% - 8px);
      left: calc(50% - 8px);
      width: 16px;
      height: 16px;
      border: 3px solid rgb(255 255 255 / 0.55);
      border-radius: 50%;
    }
  }
}

@keyframes d-phone-turn {
  from {
    transform: rotateX(-8deg) rotateY(-35deg);
  }

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