Polaroid

No dependencies
Code format
React + Tailwind
const css = `
  .card-polaroid {
    width: 168px;
    padding: 10px 10px 36px;
    border-radius: 4px;
    background: light-dark(#42454c, #e5e6e9);
    box-shadow: 0 14px 28px -12px light-dark(rgba(0, 0, 0, 0.18), rgba(0, 0, 0, 0.6)), 0 2px 4px light-dark(rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.25));
    transform: rotate(-3deg);
    transition: transform 0.5s cubic-bezier(0.34, 1.4, 0.64, 1);
    cursor: pointer;
    position: relative;
  }
  .card-polaroid:hover, .card-polaroid:focus-visible { transform: rotate(1deg) translateY(-4px); }
  .card-polaroid:focus-visible { outline: 2px solid light-dark(#42454c, #e5e6e9); outline-offset: 4px; }
  .card-polaroid__photo {
    position: relative;
    height: 132px;
    overflow: hidden;
    background: linear-gradient(light-dark(#6d7179, #7d8087), light-dark(#ebedf0, #232529) 60%, light-dark(#f3f4f7, #1a1c1f));
  }
  .card-polaroid__scene { position: absolute; inset: 0; filter: blur(9px) contrast(0.5); transition: filter 2.4s cubic-bezier(0.22, 1, 0.36, 1); }
  .card-polaroid__scene::before {
    content: '';
    position: absolute;
    left: 58%;
    top: 26px;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: light-dark(#373a40, #f1f2f3);
    box-shadow: 0 0 24px 6px color-mix(in oklab, light-dark(#373a40, #f1f2f3) 35%, transparent);
  }
  .card-polaroid__scene::after {
    content: '';
    position: absolute;
    left: -10%;
    right: -10%;
    bottom: 0;
    height: 64px;
    background: light-dark(#fbfcfd, #121417);
    clip-path: polygon(0 100%, 0 60%, 14% 40%, 30% 62%, 48% 20%, 64% 54%, 80% 34%, 100% 58%, 100% 100%);
  }
  /* The undeveloped film: a milky layer that clears as the image comes up. */
  .card-polaroid__film {
    position: absolute;
    inset: 0;
    background: light-dark(#ebedf0, #232529);
    transition: opacity 2.4s cubic-bezier(0.4, 0, 0.2, 1);
  }
  .card-polaroid:hover .card-polaroid__scene, .card-polaroid:focus-visible .card-polaroid__scene { filter: none; }
  .card-polaroid:hover .card-polaroid__film, .card-polaroid:focus-visible .card-polaroid__film { opacity: 0; }
  .card-polaroid__caption {
    position: absolute;
    left: 14px;
    right: 14px;
    bottom: 10px;
    color: light-dark(#ebedf0, #232529);
    font-family: 'Segoe Print', 'Bradley Hand', 'Comic Sans MS', cursive;
    font-size: 13px;
    transform: rotate(-1deg);
  }
  @media (prefers-reduced-motion: reduce) {
    .card-polaroid, .card-polaroid__scene, .card-polaroid__film { transition-duration: 0.01s; }
  }
`;

export function PolaroidCard() {
  return (
    <>
      <style>
        {css}
      </style>
      <div className="card-polaroid" tabIndex={0} role="img" aria-label="Instant photo: sunset over the ridge, summer 2026">
        <div className="card-polaroid__photo" aria-hidden="true">
          <div className="card-polaroid__scene" />
          <div className="card-polaroid__film" />
        </div>
        <span className="card-polaroid__caption" aria-hidden="true">
          the ridge, summer '26
        </span>
      </div>
    </>
  );
}

Free to use in any project, commercial included. MIT-0, no attribution required. Licence

Install with shadcn

npx shadcn@latest add @movin/card-polaroid
Source on GitHub

About this component

An instant photo that develops as you hover: the milky film clears and the blurred, flat image sharpens and gains contrast over a couple of seconds, while the print tilts and lifts. A handwritten caption sits on the white border.

Dependencies
None
Difficulty
Beginner
Triggers
hover, fill, card
Techniques
filter, css transition, clip path, pseudo elements
Reduced motion
Respected
Theme
Light and dark (follows your page's color-scheme)

Focusable, with role="img" and an aria-label describing the photo; focus develops it like hover. Under prefers-reduced-motion it develops instantly.

More cards

See all
Saturn1.4 billion km away
Pop-out
Poster
QR Assemble
12 minStudio · 3.2 km
Live
Route