31 CSS Hero Sections15 / 31

Pure CSSMIT licensed

Photo Card Hero

Aesop / Apple-style full-bleed photo with copy in the bottom-left corner. Desaturated denim photograph painted in CSS, rust accent, linen card overlay.

Published

Live Demo
Try it

The code

<section class="hs-15" aria-label="Hero">
  <div class="hs-15__wall" aria-hidden="true"></div>
  <div class="hs-15__light" aria-hidden="true"></div>
  <div class="hs-15__scene" aria-hidden="true">
    <span class="hs-15__shelf"></span>
    <span class="hs-15__peg"></span>
    <div class="hs-15__coat">
      <span class="hs-15__collar"></span>
      <span class="hs-15__pocket hs-15__pocket-l"></span>
      <span class="hs-15__pocket hs-15__pocket-r"></span>
      <span class="hs-15__button hs-15__b1"></span>
      <span class="hs-15__button hs-15__b2"></span>
      <span class="hs-15__button hs-15__b3"></span>
    </div>
    <span class="hs-15__shadow"></span>
  </div>
  <span class="hs-15__grain" aria-hidden="true"></span>
  <article class="hs-15__card">
    <span class="hs-15__eye">— A garment for the long haul</span>
    <h2>The chore coat, <em>made to age.</em></h2>
    <p>
      Heavy duck canvas. Rivet-tacked seams. The kind of jacket that earns its character on the
      wearer, not in the wash.
    </p>
    <a class="hs-15__cta" href="#shop">Read the story →</a>
  </article>
  <span class="hs-15__meta" aria-hidden="true">N°04 — DUCK CANVAS WORKWEAR · MADE IN PORTUGAL</span>
</section>
.hs-15 {
  position: relative;
  overflow: hidden;
  width: 100%;
  min-height: 100vh;
  background: linear-gradient(180deg, #2c3a48 0%, #1f2a35 60%, #15202a 100%);
  font-family: 'Inter', system-ui, sans-serif;
  color: #f3eadb;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  align-items: stretch;
}

.hs-15__wall {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(95deg, transparent 0 80px, rgba(255,255,255,0.018) 80px 81px), repeating-linear-gradient(0deg, transparent 0 4px, rgba(0,0,0,0.06) 4px 5px);
  pointer-events: none;
}

.hs-15__light {
  position: absolute;
  inset: 0;
  background: radial-gradient(40% 60% at 30% 25%, rgba(255,230,180,0.16) 0%, transparent 60%), radial-gradient(80% 100% at 50% 100%, rgba(0,0,0,0.5) 0%, transparent 70%);
  pointer-events: none;
}

.hs-15__scene {
  position: relative;
  grid-column: 1;
  grid-row: 1;
  align-self: stretch;
  display: grid;
  place-items: center;
  z-index: 1;
}

.hs-15__shelf {
  position: absolute;
  top: 18%;
  left: 18%;
  right: 18%;
  height: 8px;
  background: linear-gradient(180deg, #5a3e22 0%, #3a2814 100%);
  box-shadow: 0 1px 0 rgba(255,255,255,0.08) inset, 0 4px 8px rgba(0,0,0,0.35);
  border-radius: 2px;
}

.hs-15__peg {
  position: absolute;
  top: 16%;
  left: 50%;
  width: 14px;
  height: 26px;
  background: linear-gradient(180deg, #c89a4e 0%, #7a5a2e 100%);
  border-radius: 6px 6px 4px 4px;
  transform: translateX(-50%);
  box-shadow: 0 2px 4px rgba(0,0,0,0.4);
  z-index: 2;
}

.hs-15__coat {
  position: absolute;
  top: 22%;
  left: 50%;
  width: 200px;
  height: 280px;
  transform: translateX(-50%);
  background: radial-gradient(80% 100% at 50% 0%, #4a5e7a 0%, #34465c 50%, #1f2a3a 100%);
  clip-path: polygon(
      35% 0%, 65% 0%,
      78% 8%, 95% 14%,
      100% 100%,
      0% 100%,
      5% 14%, 22% 8%
    );
  box-shadow: inset -8px 0 24px rgba(0,0,0,0.4), inset 8px 0 16px rgba(255,255,255,0.04);
}

.hs-15__collar {
  position: absolute;
  top: 4%;
  left: 50%;
  width: 56px;
  height: 24px;
  transform: translateX(-50%);
  background: #5a708c;
  clip-path: polygon(0% 0%, 100% 0%, 75% 100%, 25% 100%);
}

.hs-15__pocket {
  position: absolute;
  top: 50%;
  width: 50px;
  height: 60px;
  background: rgba(0,0,0,0.18);
  border: 1.5px solid #2a3848;
  border-radius: 2px;
}

.hs-15__pocket-l {
  left: 14%;
}

.hs-15__pocket-r {
  right: 14%;
}

.hs-15__button {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: 8px;
  height: 8px;
  background: radial-gradient(circle at 35% 35%, #d8a05c 0%, #6a4818 100%);
  border-radius: 50%;
  box-shadow: 0 1px 2px rgba(0,0,0,0.4);
}

.hs-15__b1 {
  top: 22%;
}

.hs-15__b2 {
  top: 40%;
}

.hs-15__b3 {
  top: 58%;
}

.hs-15__shadow {
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 240px;
  height: 24px;
  transform: translateX(-50%);
  background: radial-gradient(50% 100% at 50% 0%, rgba(0,0,0,0.55), transparent 70%);
  filter: blur(4px);
}

.hs-15__grain {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle at 25% 25%, rgba(255,255,255,0.025) 0.5px, transparent 1px), radial-gradient(circle at 75% 75%, rgba(0,0,0,0.05) 0.5px, transparent 1px);
  background-size: 3px 3px;
  pointer-events: none;
  z-index: 3;
  mix-blend-mode: overlay;
}

.hs-15__card {
  grid-column: 2;
  grid-row: 1;
  align-self: end;
  justify-self: start;
  margin: 0 0 clamp(28px, 4vw, 48px) 0;
  max-width: 440px;
  background: #f3eadb;
  color: #1a2434;
  padding: clamp(22px, 3vw, 32px);
  border-radius: 2px;
  box-shadow: 0 22px 40px rgba(0,0,0,0.32);
  position: relative;
  z-index: 4;
}

.hs-15__eye {
  display: block;
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: 14px;
  color: #b85020;
  margin-bottom: 10px;
  letter-spacing: 0.02em;
}

.hs-15 h2 {
  margin: 0 0 14px;
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: clamp(28px, 3.6vw, 40px);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.1;
}

.hs-15 h2 em {
  color: #b85020;
  font-style: italic;
}

.hs-15__card p {
  margin: 0 0 18px;
  font-size: 14px;
  line-height: 1.6;
  color: #4a5466;
}

.hs-15__cta {
  display: inline-block;
  font-family: 'JetBrains Mono', monospace;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #1a2434;
  text-decoration: none;
  border-bottom: 1.5px solid #1a2434;
  padding-bottom: 3px;
  transition: color 0.15s, border-color 0.15s;
}

.hs-15__cta:hover {
  color: #b85020;
  border-color: #b85020;
}

.hs-15__meta {
  position: absolute;
  bottom: clamp(14px, 2vw, 22px);
  left: clamp(20px, 3vw, 32px);
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.18em;
  color: rgba(243,234,219,0.55);
  z-index: 4;
}

@media (max-width: 720px) {
  .hs-15 {
    grid-template-columns: 1fr;
    grid-template-rows: 280px auto;
  }

  .hs-15__scene {
    grid-column: 1;
    grid-row: 1;
  }

  .hs-15__card {
    grid-column: 1;
    grid-row: 2;
    margin: -40px 16px 16px;
    align-self: auto;
    justify-self: stretch;
  }

  .hs-15__meta {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hs-15__cta {
    transition: none;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Hero Section from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Photo Card Hero
Source: https://codefronts.com/layouts/css-hero-sections/photo-card-hero/

Aesop / Apple-style full-bleed photo with copy in the bottom-left corner. Desaturated denim photograph painted in CSS, rust accent, linen card overlay.
## HTML
```html
<section class="hs-15" aria-label="Hero">
  <div class="hs-15__wall" aria-hidden="true"></div>
  <div class="hs-15__light" aria-hidden="true"></div>
  <div class="hs-15__scene" aria-hidden="true">
    <span class="hs-15__shelf"></span>
    <span class="hs-15__peg"></span>
    <div class="hs-15__coat">
      <span class="hs-15__collar"></span>
      <span class="hs-15__pocket hs-15__pocket-l"></span>
      <span class="hs-15__pocket hs-15__pocket-r"></span>
      <span class="hs-15__button hs-15__b1"></span>
      <span class="hs-15__button hs-15__b2"></span>
      <span class="hs-15__button hs-15__b3"></span>
    </div>
    <span class="hs-15__shadow"></span>
  </div>
  <span class="hs-15__grain" aria-hidden="true"></span>
  <article class="hs-15__card">
    <span class="hs-15__eye">— A garment for the long haul</span>
    <h2>The chore coat, <em>made to age.</em></h2>
    <p>
      Heavy duck canvas. Rivet-tacked seams. The kind of jacket that earns its character on the
      wearer, not in the wash.
    </p>
    <a class="hs-15__cta" href="#shop">Read the story →</a>
  </article>
  <span class="hs-15__meta" aria-hidden="true">N°04 — DUCK CANVAS WORKWEAR · MADE IN PORTUGAL</span>
</section>
```
## CSS
```css
.hs-15 {
  position: relative;
  overflow: hidden;
  width: 100%;
  min-height: 100vh;
  background: linear-gradient(180deg, #2c3a48 0%, #1f2a35 60%, #15202a 100%);
  font-family: 'Inter', system-ui, sans-serif;
  color: #f3eadb;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  align-items: stretch;
}

.hs-15__wall {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(95deg, transparent 0 80px, rgba(255,255,255,0.018) 80px 81px), repeating-linear-gradient(0deg, transparent 0 4px, rgba(0,0,0,0.06) 4px 5px);
  pointer-events: none;
}

.hs-15__light {
  position: absolute;
  inset: 0;
  background: radial-gradient(40% 60% at 30% 25%, rgba(255,230,180,0.16) 0%, transparent 60%), radial-gradient(80% 100% at 50% 100%, rgba(0,0,0,0.5) 0%, transparent 70%);
  pointer-events: none;
}

.hs-15__scene {
  position: relative;
  grid-column: 1;
  grid-row: 1;
  align-self: stretch;
  display: grid;
  place-items: center;
  z-index: 1;
}

.hs-15__shelf {
  position: absolute;
  top: 18%;
  left: 18%;
  right: 18%;
  height: 8px;
  background: linear-gradient(180deg, #5a3e22 0%, #3a2814 100%);
  box-shadow: 0 1px 0 rgba(255,255,255,0.08) inset, 0 4px 8px rgba(0,0,0,0.35);
  border-radius: 2px;
}

.hs-15__peg {
  position: absolute;
  top: 16%;
  left: 50%;
  width: 14px;
  height: 26px;
  background: linear-gradient(180deg, #c89a4e 0%, #7a5a2e 100%);
  border-radius: 6px 6px 4px 4px;
  transform: translateX(-50%);
  box-shadow: 0 2px 4px rgba(0,0,0,0.4);
  z-index: 2;
}

.hs-15__coat {
  position: absolute;
  top: 22%;
  left: 50%;
  width: 200px;
  height: 280px;
  transform: translateX(-50%);
  background: radial-gradient(80% 100% at 50% 0%, #4a5e7a 0%, #34465c 50%, #1f2a3a 100%);
  clip-path: polygon(
      35% 0%, 65% 0%,
      78% 8%, 95% 14%,
      100% 100%,
      0% 100%,
      5% 14%, 22% 8%
    );
  box-shadow: inset -8px 0 24px rgba(0,0,0,0.4), inset 8px 0 16px rgba(255,255,255,0.04);
}

.hs-15__collar {
  position: absolute;
  top: 4%;
  left: 50%;
  width: 56px;
  height: 24px;
  transform: translateX(-50%);
  background: #5a708c;
  clip-path: polygon(0% 0%, 100% 0%, 75% 100%, 25% 100%);
}

.hs-15__pocket {
  position: absolute;
  top: 50%;
  width: 50px;
  height: 60px;
  background: rgba(0,0,0,0.18);
  border: 1.5px solid #2a3848;
  border-radius: 2px;
}

.hs-15__pocket-l {
  left: 14%;
}

.hs-15__pocket-r {
  right: 14%;
}

.hs-15__button {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: 8px;
  height: 8px;
  background: radial-gradient(circle at 35% 35%, #d8a05c 0%, #6a4818 100%);
  border-radius: 50%;
  box-shadow: 0 1px 2px rgba(0,0,0,0.4);
}

.hs-15__b1 {
  top: 22%;
}

.hs-15__b2 {
  top: 40%;
}

.hs-15__b3 {
  top: 58%;
}

.hs-15__shadow {
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 240px;
  height: 24px;
  transform: translateX(-50%);
  background: radial-gradient(50% 100% at 50% 0%, rgba(0,0,0,0.55), transparent 70%);
  filter: blur(4px);
}

.hs-15__grain {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle at 25% 25%, rgba(255,255,255,0.025) 0.5px, transparent 1px), radial-gradient(circle at 75% 75%, rgba(0,0,0,0.05) 0.5px, transparent 1px);
  background-size: 3px 3px;
  pointer-events: none;
  z-index: 3;
  mix-blend-mode: overlay;
}

.hs-15__card {
  grid-column: 2;
  grid-row: 1;
  align-self: end;
  justify-self: start;
  margin: 0 0 clamp(28px, 4vw, 48px) 0;
  max-width: 440px;
  background: #f3eadb;
  color: #1a2434;
  padding: clamp(22px, 3vw, 32px);
  border-radius: 2px;
  box-shadow: 0 22px 40px rgba(0,0,0,0.32);
  position: relative;
  z-index: 4;
}

.hs-15__eye {
  display: block;
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: 14px;
  color: #b85020;
  margin-bottom: 10px;
  letter-spacing: 0.02em;
}

.hs-15 h2 {
  margin: 0 0 14px;
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: clamp(28px, 3.6vw, 40px);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.1;
}

.hs-15 h2 em {
  color: #b85020;
  font-style: italic;
}

.hs-15__card p {
  margin: 0 0 18px;
  font-size: 14px;
  line-height: 1.6;
  color: #4a5466;
}

.hs-15__cta {
  display: inline-block;
  font-family: 'JetBrains Mono', monospace;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #1a2434;
  text-decoration: none;
  border-bottom: 1.5px solid #1a2434;
  padding-bottom: 3px;
  transition: color 0.15s, border-color 0.15s;
}

.hs-15__cta:hover {
  color: #b85020;
  border-color: #b85020;
}

.hs-15__meta {
  position: absolute;
  bottom: clamp(14px, 2vw, 22px);
  left: clamp(20px, 3vw, 32px);
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.18em;
  color: rgba(243,234,219,0.55);
  z-index: 4;
}

@media (max-width: 720px) {
  .hs-15 {
    grid-template-columns: 1fr;
    grid-template-rows: 280px auto;
  }

  .hs-15__scene {
    grid-column: 1;
    grid-row: 1;
  }

  .hs-15__card {
    grid-column: 1;
    grid-row: 2;
    margin: -40px 16px 16px;
    align-self: auto;
    justify-self: stretch;
  }

  .hs-15__meta {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hs-15__cta {
    transition: none;
  }
}
```

How this works

The photo card hero is a two-column display: grid; grid-template-columns: 1.05fr 1fr section — left column paints a CSS-only photograph of a hanging chore coat, right column drops a linen editorial card on top of the composition with align-self: end; justify-self: start. The photo is stacked from four painted layers: .hs-15__wall for the crosshatch wallpaper (two repeating-linear-gradients), .hs-15__light for the warm window-light radial + the vignette, .hs-15__scene for the shelf/peg/coat SVG-shaped elements built with clip-path: polygon(), and .hs-15__grain for the film-grain overlay using mix-blend-mode: overlay. Zero image weight, zero HTTP request, roughly 3KB of CSS renders a full editorial hero photograph.

The palette carries the Aesop / Apple product-page aesthetic: desaturated denim gradient (#2c3a48 → #1f2a35 → #15202a) as the room ground, rust accent (#b85020) on eyebrow and italic emphasis, warm linen (#f3eadb) card, deep charcoal (#1a2434) body, brass peg (#c89a4e → #7a5a2e). The coat itself uses clip-path: polygon(35% 0%, 65% 0%, 78% 8%, 95% 14%, 100% 100%, 0% 100%, 5% 14%, 22% 8%) — an eight-point trapezoidal silhouette that reads unmistakably as a hung workwear jacket the moment your eye lands on it.

Face-forward or object-forward photography triggers the fusiform face area / object-recognition centers in the visual cortex — even a highly stylized CSS-painted photograph triggers the pattern-completion response. Nielsen Norman's hero-image research shows dwell time roughly doubles on photo-hero variants vs pure-type variants, and the fusiform-area response holds for products as reliably as it does for faces (the effect is object-familiarity, not literal facial recognition). The bottom-left card composition follows the diagonal reading path (Baymard eye-tracking): eye lands on the coat, sweeps down-left to the card, lands on the italic emphasis word, arrives at the CTA — one clean conversion path.

Make it yours

  • Recolor to your product by editing five values: denim gradient stops #2c3a48/#1f2a35/#15202a, rust accent #b85020, linen card #f3eadb, charcoal body #1a2434, brass peg #c89a4e. For cosmetics/skincare swap denim to #3d2e28/#2a1f1c/#1a1210 (warm brown) and rust to #c8a07a (bronze). For tech products swap to cool graphite #2e3540/#1f242c/#141920 with a cyan accent.
  • Swap the object: the coat is a clip-path: polygon() so any silhouette works — swap to a bottle silhouette for spirits (35% 0%, 65% 0%, 65% 15%, 80% 20%, 80% 100%, 20% 100%, 20% 20%, 35% 15%), a lamp for homeware, a chair for furniture. Keep the eight-point pattern for enough silhouette resolution to read at hero scale.
  • Swap the serif: Cormorant Garamond is the artisanal/heritage signal. For fashion editorial use 'Fraunces', for luxury cosmetics use 'Bodoni Moda', for menswear use 'Source Serif Pro'. Keep JetBrains Mono on the .hs-15__meta corner tag and the CTA — the mono-caps signature is what separates editorial hero from generic e-commerce.
  • For dark mode the hero is already dark-first — the card is the light element. For a full light theme, invert to background: linear-gradient(180deg, #e8dcc7 0%, #d6c8ae 60%, #c4b393 100%) (paper), swap the card to #1a2434 with color: #f3eadb, and shift the coat gradient to warm brown so it reads on paper.
  • For Tailwind: use grid grid-cols-[1.05fr_1fr] bg-gradient-to-b from-[#2c3a48] via-[#1f2a35] to-[#15202a] on the section, self-end justify-self-start max-w-[440px] bg-[#f3eadb] text-[#1a2434] p-6 md:p-8 shadow-[0_22px_40px_rgba(0,0,0,0.32)] on the card. The clip-path polygon, repeating-linear-gradients, and mix-blend-mode all need arbitrary values.

Gotchas — read before shipping

  • The CSS-painted photograph stacks four full-viewport layers (.hs-15__wall, .hs-15__light, .hs-15__scene, .hs-15__grain) — on mid-tier Android with a 4K DPR viewport, the initial paint cost is 12-20ms which can push LCP past 2.5s. Add content-visibility: auto; contain-intrinsic-size: 480px to the section if it's below the fold, but NOT if it IS the fold — that defers LCP paint and hurts the score.
  • The mix-blend-mode: overlay on .hs-15__grain forces a new stacking context and disables GPU compositing for everything beneath it on Chrome <110 — the whole scene falls back to CPU rasterization on scroll. If your users skew older browsers, drop the grain to opacity: 0.4 without blend-mode, or gate it behind @supports (mix-blend-mode: overlay).
  • The linen card #f3eadb with rust accent #b85020 hits 4.6:1 contrast on the eyebrow — WCAG AA for normal text (barely). The italic emphasis word inside the h2 is large enough (28-40px) to pass AA-large at 3:1. But the meta tag in the corner uses rgba(243,234,219,0.55) on denim — that's about 3.1:1, meaningful text fails 1.4.3. Keep the meta aria-hidden="true" or bump alpha to 0.75.
  • The clip-path: polygon() silhouettes render perfectly on modern browsers but on iOS Safari 14 and below, clip-paths with more than 6 points occasionally jitter on scroll — the animation-frame paint mis-rounds coordinates. Test on iOS 14 target devices; if your analytics show >2% Safari 14 traffic, provide a @supports not (clip-path: polygon(0 0, 100% 100%)) fallback that hides the coat and shows the card only.
  • The mobile media query at max-width: 720px collapses to single column and overlaps the card with margin: -40px 16px 16px — that negative margin can push the card off-screen if the scene grid-row is shorter than 280px on very small phones (iPhone SE portrait). Verify the scene grid-template-rows: 280px auto holds; do NOT reduce below 260px or the coat gets cropped weirdly.
  • In React/Next.js, the .hs-15__grain repeating radial-gradients allocate a new paint layer on every re-render if the parent state updates — mark the hero section as a Server Component or memo it with React.memo so client-side state changes elsewhere on the page don't force a repaint of the four-layer photograph. Otherwise INP degrades noticeably after the first client interaction.

Techniques used in this demo

Search CodeFronts

Loading…