23 CSS Flip Cards 05 / 23

Booking Event Ticket Fold Card

A concert ticket split into two halves that bi-fold inward like a book page, each half rotating around its centre edge to reveal the ticket details on the back.

Pure CSS MIT licensed
Live Demo Open in tab
Open in playground

The code

<div class="fc-22">
  <div class="fc-22__scene">
    <div class="fc-22__book">

      <!-- LEFT HALF -->
      <div class="fc-22__half fc-22__half--left">
        <!-- FRONT face -->
        <div class="fc-22__front-left">
          <div class="fc-22__event-cat">Live Concert · 2025</div>
          <div class="fc-22__event-name">Neon Pulse<br>World Tour</div>
          <div class="fc-22__event-sub">The Meridian Arena · San Francisco</div>
          <div class="fc-22__meta-row">
            <div class="fc-22__meta">
              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
              Sat, Nov 29 · 8:00 PM
            </div>
          </div>
          <div class="fc-22__meta-row">
            <div class="fc-22__meta">
              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
              Floor · General Admission
            </div>
          </div>
        </div>
        <!-- BACK face (rotated 180deg inside) -->
        <div class="fc-22__back-left">
          <div class="fc-22__back-cat">Ticket Details</div>
          <div class="fc-22__back-title">Neon Pulse · SF</div>
          <div class="fc-22__detail-grid">
            <div class="fc-22__detail-item">
              <div class="fc-22__di-label">Date</div>
              <div class="fc-22__di-val">Nov 29</div>
            </div>
            <div class="fc-22__detail-item">
              <div class="fc-22__di-label">Doors</div>
              <div class="fc-22__di-val fc-22__di-val--sky">6:30 PM</div>
            </div>
            <div class="fc-22__detail-item">
              <div class="fc-22__di-label">Zone</div>
              <div class="fc-22__di-val">Floor A</div>
            </div>
            <div class="fc-22__detail-item">
              <div class="fc-22__di-label">Price</div>
              <div class="fc-22__di-val fc-22__di-val--sky">$149</div>
            </div>
          </div>
        </div>
      </div><!-- /left half -->

      <!-- RIGHT HALF (stub) -->
      <div class="fc-22__half fc-22__half--right">
        <div class="fc-22__perfs">
          <div class="fc-22__perf"></div>
          <div class="fc-22__perf"></div>
          <div class="fc-22__perf"></div>
          <div class="fc-22__perf"></div>
          <div class="fc-22__perf"></div>
        </div>
        <!-- FRONT stub -->
        <div class="fc-22__front-right">
          <div class="fc-22__qr-wrap">
            <!-- mini QR SVG -->
            <svg viewBox="0 0 17 17" width="56" height="56" fill="black">
              <rect x="0" y="0" width="5" height="5" fill="none" stroke="black" stroke-width=".8"/>
              <rect x="1.5" y="1.5" width="2" height="2"/>
              <rect x="12" y="0" width="5" height="5" fill="none" stroke="black" stroke-width=".8"/>
              <rect x="13.5" y="1.5" width="2" height="2"/>
              <rect x="0" y="12" width="5" height="5" fill="none" stroke="black" stroke-width=".8"/>
              <rect x="1.5" y="13.5" width="2" height="2"/>
              <rect x="6" y="0" width="1" height="1"/><rect x="7" y="1" width="1" height="1"/>
              <rect x="9" y="0" width="1" height="1"/><rect x="10" y="1" width="1" height="1"/>
              <rect x="6" y="2" width="2" height="1"/><rect x="9" y="3" width="1" height="1"/>
              <rect x="6" y="6" width="1" height="1"/><rect x="8" y="6" width="1" height="1"/><rect x="10" y="6" width="1" height="1"/>
              <rect x="7" y="7" width="2" height="1"/><rect x="11" y="7" width="1" height="1"/>
              <rect x="6" y="8" width="1" height="1"/><rect x="9" y="8" width="1" height="1"/><rect x="12" y="8" width="1" height="1"/>
              <rect x="6" y="10" width="2" height="1"/><rect x="10" y="10" width="1" height="1"/>
              <rect x="7" y="12" width="1" height="1"/><rect x="9" y="12" width="1" height="1"/><rect x="11" y="12" width="1" height="1"/>
              <rect x="6" y="13" width="1" height="1"/><rect x="10" y="13" width="2" height="1"/>
              <rect x="6" y="15" width="1" height="1"/><rect x="8" y="15" width="1" height="1"/><rect x="11" y="15" width="1" height="1"/>
              <rect x="12" y="6" width="5" height="5" fill="none" stroke="black" stroke-width=".8"/>
              <rect x="13.5" y="7.5" width="2" height="2"/>
            </svg>
          </div>
          <div class="fc-22__seat">B14</div>
          <div class="fc-22__seat-label">Seat</div>
          <div class="fc-22__stub-label">Scan at entry</div>
        </div>
        <!-- BACK stub -->
        <div class="fc-22__back-right">
          <div class="fc-22__barcode">
            <div class="fc-22__bar" style="width:2px;height:52px"></div>
            <div class="fc-22__bar" style="width:3px;height:40px"></div>
            <div class="fc-22__bar" style="width:2px;height:52px"></div>
            <div class="fc-22__bar" style="width:1px;height:32px"></div>
            <div class="fc-22__bar" style="width:3px;height:48px"></div>
            <div class="fc-22__bar" style="width:2px;height:44px"></div>
            <div class="fc-22__bar" style="width:1px;height:52px"></div>
            <div class="fc-22__bar" style="width:2px;height:36px"></div>
            <div class="fc-22__bar" style="width:3px;height:52px"></div>
            <div class="fc-22__bar" style="width:1px;height:44px"></div>
            <div class="fc-22__bar" style="width:2px;height:40px"></div>
            <div class="fc-22__bar" style="width:3px;height:52px"></div>
            <div class="fc-22__bar" style="width:1px;height:34px"></div>
            <div class="fc-22__bar" style="width:2px;height:48px"></div>
          </div>
          <div class="fc-22__barcode-num">4829-1104-B14</div>
          <div class="fc-22__back-seat">B14</div>
          <div class="fc-22__back-seat-label">Confirmed Seat</div>
          <div class="fc-22__valid-badge">
            <span class="fc-22__valid-dot"></span>Valid Entry
          </div>
        </div>
      </div><!-- /right half -->

      <div class="fc-22__spine"></div>
    </div><!-- /book -->
    <div class="fc-22__fold-hint">Hover to unfold</div>
  </div><!-- /scene -->
</div>
.fc-22,.fc-22 *,.fc-22 *::before,.fc-22 *::after{box-sizing:border-box;margin:0;padding:0}
.fc-22 ::selection{background:#f97316;color:#fff}
.fc-22{
  --bg:#07080f;
  --orange:#f97316;
  --amber:#fbbf24;
  --sky:#38bdf8;
  --rose:#fb7185;
  --white:#fff7ed;
  --card-w:420px;
  --card-h:200px;
  font-family:'Segoe UI',system-ui,sans-serif;
  background:radial-gradient(ellipse at 50% 30%,#1a0e00,#07080f 65%);
  min-height:100vh;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:40px 20px;gap:28px;
  color:var(--white);
}

/* ══════════════════════════════════════════
   PAGE-FOLD MECHANISM
   The card is split into LEFT half and RIGHT half.
   Each half is a flex child inside the card wrapper.
   On hover:
     • Left half rotates  rotateY(-180deg) around its RIGHT edge  (transform-origin: right)
     • Right half rotates rotateY( 180deg) around its LEFT  edge  (transform-origin: left)
   Both use preserve-3d so their ::before back-faces show through.
   Result: a bi-fold page-turn that meets in the middle.
══════════════════════════════════════════ */

.fc-22__scene{
  width:var(--card-w);
  height:var(--card-h);
  position:relative;
  /* perspective on the scene keeps both halves in one shared 3D space */
  perspective:1400px;
  perspective-origin:50% 50%;
  cursor:pointer;
}

/* Outer wrapper — just a flex row holding the two halves */
.fc-22__book{
  width:100%;height:100%;
  display:flex;
  position:relative;
}

/* ── shared half styles ── */
.fc-22__half{
  width:50%;height:100%;
  position:relative;
  transform-style:preserve-3d;
  transition:transform .7s cubic-bezier(.4,0,.2,1);
}

/* LEFT half pivots around its right edge. Background/border now live
   on each face div (front/back) — keeping them on the half itself would
   show as a solid coloured rectangle through transparent regions when
   the half rotates 180°. */
.fc-22__half--left{
  transform-origin:right center;
}

/* RIGHT half pivots around its left edge. Background/border on each
   face div (front/back) — see comment on --left above. */
.fc-22__half--right{
  transform-origin:left center;
}

/* On hover: left folds away (rotates around right edge by -180deg),
             right folds away (rotates around left edge by +180deg) */
.fc-22__scene:hover .fc-22__half--left{transform:rotateY(-180deg)}
.fc-22__scene:hover .fc-22__half--right{transform:rotateY(180deg)}

/* ── FRONT content — lives on the element itself (z-index front face) ── */
.fc-22__front-left{
  position:absolute;inset:0;
  background:linear-gradient(160deg,#1e1000,#120900);
  border:1px solid rgba(249,115,22,.25);
  border-right:none;
  border-radius:12px 0 0 12px;
  display:flex;flex-direction:column;justify-content:center;padding:20px 20px 20px 24px;gap:8px;
  backface-visibility:hidden;-webkit-backface-visibility:hidden;
}
.fc-22__front-right{
  position:absolute;inset:0;
  background:linear-gradient(160deg,#120900,#0d0700);
  border:1px solid rgba(249,115,22,.25);
  border-left:1px dashed rgba(249,115,22,.3);
  border-radius:0 12px 12px 0;
  display:flex;flex-direction:column;justify-content:center;align-items:center;
  padding:16px;gap:10px;
  backface-visibility:hidden;-webkit-backface-visibility:hidden;
}

/* ── BACK content — ::before pseudo, pre-rotated 180deg so it faces inward ── */
/* Back content lives in .fc-22__back-* divs pre-rotated 180deg. */

.fc-22__back-left,
.fc-22__back-right{
  position:absolute;inset:0;
  backface-visibility:hidden;-webkit-backface-visibility:hidden;
  transform:rotateY(180deg);
  display:flex;flex-direction:column;justify-content:center;
}

.fc-22__back-left{
  background:linear-gradient(160deg,#0a1a28,#061020);
  border-radius:12px 0 0 12px;
  padding:20px 18px 20px 24px;gap:8px;
  border-right:1px dashed rgba(56,189,248,.3);
}
.fc-22__back-right{
  background:linear-gradient(160deg,#061020,#04090f);
  border-radius:0 12px 12px 0;
  padding:16px;gap:10px;
  align-items:center;
}

/* ── FRONT — left content ── */
.fc-22__event-cat{font-size:9px;letter-spacing:.18em;text-transform:uppercase;color:var(--orange);font-weight:700}
.fc-22__event-name{font-size:19px;font-weight:900;color:var(--white);line-height:1.15}
.fc-22__event-sub{font-size:11px;color:rgba(255,247,237,.5)}
.fc-22__meta-row{display:flex;gap:14px;margin-top:4px}
.fc-22__meta{display:flex;align-items:center;gap:5px;font-size:11px;color:rgba(255,247,237,.55)}
.fc-22__meta svg{flex-shrink:0;opacity:.7}

/* ── FRONT — right content (stub) ── */
.fc-22__qr-wrap{
  width:68px;height:68px;background:#fff;border-radius:8px;
  padding:5px;display:flex;align-items:center;justify-content:center;
}
.fc-22__stub-label{font-size:9px;letter-spacing:.14em;text-transform:uppercase;color:rgba(255,247,237,.35);text-align:center}
.fc-22__seat{font-size:22px;font-weight:900;color:var(--amber);text-align:center;line-height:1}
.fc-22__seat-label{font-size:9px;color:rgba(255,247,237,.35);letter-spacing:.1em;text-transform:uppercase}
.fc-22__fold-hint{
  position:absolute;bottom:10px;left:50%;transform:translateX(-50%);
  font-size:9px;letter-spacing:.1em;text-transform:uppercase;
  color:rgba(255,247,237,.2);white-space:nowrap;
}

/* ── BACK — left content ── */
.fc-22__back-cat{font-size:9px;letter-spacing:.18em;text-transform:uppercase;color:var(--sky);font-weight:700}
.fc-22__back-title{font-size:17px;font-weight:900;color:var(--white);line-height:1.2}
.fc-22__detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:4px}
.fc-22__detail-item{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);border-radius:8px;padding:8px 10px}
.fc-22__di-label{font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:rgba(255,247,237,.35);margin-bottom:2px}
.fc-22__di-val{font-size:13px;font-weight:700;color:var(--white)}
.fc-22__di-val--sky{color:var(--sky)}

/* ── BACK — right content ── */
.fc-22__barcode{
  display:flex;gap:2px;align-items:flex-end;height:52px;
}
.fc-22__bar{border-radius:1px;background:var(--white)}
.fc-22__barcode-num{font-size:9px;letter-spacing:.08em;color:rgba(255,247,237,.35);font-family:'Courier New',monospace;margin-top:4px}
.fc-22__back-seat{font-size:28px;font-weight:900;color:var(--sky);line-height:1}
.fc-22__back-seat-label{font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:rgba(255,247,237,.35)}
.fc-22__valid-badge{
  display:flex;align-items:center;gap:5px;
  background:rgba(56,189,248,.1);border:1px solid rgba(56,189,248,.25);
  border-radius:20px;padding:4px 10px;
  font-size:10px;font-weight:700;color:var(--sky);
}
.fc-22__valid-dot{width:6px;height:6px;border-radius:50%;background:var(--sky)}

/* Centre join shadow — gives the fold a spine */
.fc-22__spine{
  position:absolute;left:50%;top:0;bottom:0;width:2px;
  background:linear-gradient(180deg,transparent,rgba(249,115,22,.25),transparent);
  transform:translateX(-50%);
  pointer-events:none;z-index:10;
}

/* Perforations on the right half left edge */
.fc-22__perfs{
  position:absolute;left:-1px;top:0;bottom:0;
  display:flex;flex-direction:column;justify-content:space-between;padding:10px 0;
  pointer-events:none;z-index:5;
}
.fc-22__perf{
  width:10px;height:10px;border-radius:50%;
  background:var(--bg);
  border:1px solid rgba(249,115,22,.2);
  margin-left:-5px;
}

@media(max-width:480px){
  .fc-22{--card-w:340px;--card-h:180px}
  .fc-22__event-name{font-size:15px}
}
@media(prefers-reduced-motion:reduce){
  .fc-22__half--left,.fc-22__half--right{transition:none}
}

How this works

The card is a flex row of two .fcn-01__half siblings. The left half sets transform-origin: right center and on hover receives transform: rotateY(-180deg); the right half sets transform-origin: left center and receives transform: rotateY(180deg). Both use transform-style: preserve-3d and share the single perspective: 1400px on their parent scene, placing them in one shared vanishing-point space so the fold reads as a unified bi-fold action.

Front content sits in a child div with backface-visibility: hidden facing forward. Back content sits in a sibling div pre-rotated rotateY(180deg) so it starts face-down, also with backface-visibility: hidden. The dashed border on the right-half left edge and the semi-circle perforations simulate a real ticket stub tear line, and a one-pixel spine div between the halves adds a centre crease shadow.

Customize

  • Change the fold speed by editing transition: transform .7s cubic-bezier(.4,0,.2,1) on .fcn-01__half — try 1s with ease-in-out for a slower, more theatrical fold.
  • Convert to a tri-fold by adding a third .fcn-01__half at width:33.33% with transform-origin: left center and a staggered transition-delay so it folds last.
  • Swap the event SVG QR for a real scannable code by generating a QR SVG from a ticket URL using any client-side QR library and injecting it into .fcn-01__qr-wrap.
  • Add a torn-paper edge to the stub separator by replacing the dashed border with an SVG path of jagged points using a zigzag wave pattern.
  • Animate the perforations on fold using a transition: opacity .3s that fades them to 0 when .fcn-01__scene:hover is active, so they disappear as the halves rotate.

Watch out for

  • The perspective must live on the outer .fcn-01__scene parent, not on the .fcn-01__book flex wrapper — setting it on the flex container causes each half to perspective independently and the fold looks broken.
  • Back-face content divs must be placed as siblings inside each .fcn-01__half with their own transform: rotateY(180deg) pre-rotation — using ::before pseudo-elements for back content does not work because pseudo-elements cannot contain complex HTML children.
  • overflow: hidden on .fcn-01__half is required to clip the perforations and rounded corners, but it creates a new stacking context that can interfere with preserve-3d in some WebKit versions — test on Safari and fall back to clip-path: inset(0 round 12px) if needed.

Browser support

ChromeSafariFirefoxEdge
36+ 9+ 16+ 36+

Bi-fold uses standard preserve-3d and transform-origin; universally supported. The dashed border stub and perforations are pure CSS with no browser caveats.

Search CodeFronts

Loading…