Pure TailwindMIT licensed

Tailwind Wave Section Divider with an Overlay Background Image

A crisp wave over a full-bleed photo, with the contrast scrim, focal-point crop and LCP hygiene handled properly — the combination that usually goes wrong when a divider is dropped onto imagery. The second variant masks the photo itself into a wave-edged ribbon.

Live Demo
Plain CSSTailwind v4
Try it

Complete Tailwind CSS landing page templates in HTML, React, Next.js and Vue · from $49

Cruip · Affiliate link

Browse Cruip templates

The code

<section class="twd-13 w-full min-h-svh flex flex-col bg-twd13-paper font-twd13-sans">

  <!-- Photo hero with scrim + wave -->
  <header class="relative isolate overflow-hidden text-white px-6 pt-21 pb-[clamp(96px,13vw,150px)] text-center before:content-[''] before:absolute before:inset-0 before:-z-10 before:bg-[linear-gradient(180deg,oklch(0.15_0.03_260/0.3),oklch(0.13_0.03_260/0.82))]">
    <img src="https://picsum.photos/seed/twd13a/1600/900" width="1600" height="900" alt="" fetchpriority="high" decoding="async" class="absolute inset-0 -z-20 w-full h-full object-cover object-[50%_35%]">
    <div class="relative max-w-[46ch] mx-auto">
      <p class="text-xs tracking-[0.2em] uppercase text-twd13-eyebrow mb-3">Full-bleed imagery</p>
      <h2 class="text-[clamp(27px,4.6vw,46px)] leading-[1.06] font-semibold mb-2.5 [text-shadow:0_2px_18px_oklch(0.15_0.03_260/0.55)]">Legible over any photo</h2>
      <p class="text-[15px]">Gradient scrim below, wave on top, text in the safe zone.</p>
    </div>
    <svg class="absolute inset-x-0 -bottom-px z-10 block w-full h-[clamp(56px,9vw,116px)] text-twd13-next [&_path]:fill-current pointer-events-none" viewBox="0 0 1440 120" preserveAspectRatio="none" aria-hidden="true" focusable="false">
      <path d="M0,62 C280,118 560,10 840,54 C1080,92 1250,106 1440,74 L1440,120 L0,120 Z"></path>
    </svg>
  </header>
  <div class="bg-twd13-next text-twd13-nextink px-6 pt-6 pb-15 text-center">
    <p class="text-[15px]">The wave fill matches this section exactly &mdash; no sticker effect.</p>
  </div>

  <!-- Wave-masked image band -->
  <div class="bg-twd13-paper text-twd13-paperink px-6 py-10 text-center">
    <p class="text-[15px]">Text section</p>
  </div>
  <div role="img" aria-label="Coastline photograph shown inside a wave-shaped band" class="-my-6 h-[clamp(220px,32vw,380px)] bg-twd13-bandfallback bg-[url('https://picsum.photos/seed/twd13b/1600/700')] bg-cover bg-center bg-no-repeat [mask:var(--twd13-band)_no-repeat_center/100%_100%]"></div>
  <div class="bg-twd13-paper text-twd13-paperink px-6 py-10 text-center">
    <p class="text-[15px] max-w-[52ch] mx-auto">The photo itself is masked into a wave band &mdash; both edges curved, no SVG node.</p>
  </div>

</section>
@import "tailwindcss";

@theme {
  --color-twd13-next: #f6f7fa;
  --color-twd13-next: oklch(0.97 0.01 250);
  --color-twd13-nextink: #3f4655;
  --color-twd13-nextink: oklch(0.42 0.03 250);
  --color-twd13-eyebrow: #dfe4ee;
  --color-twd13-eyebrow: oklch(0.91 0.01 250);
  --color-twd13-paper: #f3f5f8;
  --color-twd13-paper: oklch(0.96 0.01 230);
  --color-twd13-paperink: #414a55;
  --color-twd13-paperink: oklch(0.43 0.03 230);
  --color-twd13-bandfallback: #35657f;
  --color-twd13-bandfallback: oklch(0.45 0.07 230);
  --font-twd13-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* The ribbon cutter: a wave on the top AND bottom edge of one mask. */

.twd-13 {
  width: 100%;
  min-height: 100svh;
  --twd13-band: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201440%20400'%20preserveAspectRatio='none'%3E%3Cpath%20d='M0,70%20C360,-10%20700,120%201000,70%20C1180,40%201300,40%201440,66%20L1440,330%20C1200,410%20900,270%20620,326%20C420,366%20180,366%200,334%20Z'%20fill='%23000'/%3E%3C/svg%3E");
}
/* Safari < 15.4 mask shim for the photo band. */

.twd-13 [role="img"] {
  -webkit-mask: var(--twd13-band) no-repeat center/100% 100%;
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-twd13-next: #f6f7fa;
  --color-twd13-next: oklch(0.97 0.01 250);
  --color-twd13-nextink: #3f4655;
  --color-twd13-nextink: oklch(0.42 0.03 250);
  --color-twd13-eyebrow: #dfe4ee;
  --color-twd13-eyebrow: oklch(0.91 0.01 250);
  --color-twd13-paper: #f3f5f8;
  --color-twd13-paper: oklch(0.96 0.01 230);
  --color-twd13-paperink: #414a55;
  --color-twd13-paperink: oklch(0.43 0.03 230);
  --color-twd13-bandfallback: #35657f;
  --color-twd13-bandfallback: oklch(0.45 0.07 230);
  --font-twd13-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* The ribbon cutter: a wave on the top AND bottom edge of one mask. */

.twd-13 {
  width: 100%;
  min-height: 100svh;
  --twd13-band: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201440%20400'%20preserveAspectRatio='none'%3E%3Cpath%20d='M0,70%20C360,-10%20700,120%201000,70%20C1180,40%201300,40%201440,66%20L1440,330%20C1200,410%20900,270%20620,326%20C420,366%20180,366%200,334%20Z'%20fill='%23000'/%3E%3C/svg%3E");
}
/* Safari < 15.4 mask shim for the photo band. */

.twd-13 [role="img"] {
  -webkit-mask: var(--twd13-band) no-repeat center/100% 100%;
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • The scrim as a before: pseudo-element with -z-10 is a faithful port, and having isolate in the same class list as the two negative z-indexes it contains is a real readability gain — that relationship is invisible in a stylesheet.
  • object-[50%_35%] for the focal point is the kind of value that belongs inline: it is a decision about one photograph, not a design-system token.
  • The band's mask stays a custom property for the same reason as 01 and 09 — a 240-character data URI inside a class attribute is not something anyone should have to read twice.
  • UI change from the CSS original: the masked block and the photo band now sit on a root that carries the page colour, so the transparent tail below each curve reveals the next section's paper rather than a white band — the original only worked on a white page.
  • UI change from the CSS original: the hero eyebrow was opacity:.78 white and the body copy .88, both of which drift under the lighter top of the scrim. Both are now full-opacity (a token for the eyebrow, plain white for the copy), and the scrim's bottom stop went from 0.8 to 0.82 to hold the headline at AAA over a bright photo.
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 demo 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: Tailwind Wave Section Divider with an Overlay Background Image
Source: https://codefronts.com/layouts/tailwind-css-wave-section-dividers/tailwind-wave-section-divider-overlay-background-image/

A crisp wave over a full-bleed photo, with the contrast scrim, focal-point crop and LCP hygiene handled properly &mdash; the combination that usually goes wrong when a divider is dropped onto imagery. The second variant masks the photo itself into a wave-edged ribbon.
## HTML
```html
<section class="twd-13 w-full min-h-svh flex flex-col bg-twd13-paper font-twd13-sans">

  <!-- Photo hero with scrim + wave -->
  <header class="relative isolate overflow-hidden text-white px-6 pt-21 pb-[clamp(96px,13vw,150px)] text-center before:content-[''] before:absolute before:inset-0 before:-z-10 before:bg-[linear-gradient(180deg,oklch(0.15_0.03_260/0.3),oklch(0.13_0.03_260/0.82))]">
    <img src="https://picsum.photos/seed/twd13a/1600/900" width="1600" height="900" alt="" fetchpriority="high" decoding="async" class="absolute inset-0 -z-20 w-full h-full object-cover object-[50%_35%]">
    <div class="relative max-w-[46ch] mx-auto">
      <p class="text-xs tracking-[0.2em] uppercase text-twd13-eyebrow mb-3">Full-bleed imagery</p>
      <h2 class="text-[clamp(27px,4.6vw,46px)] leading-[1.06] font-semibold mb-2.5 [text-shadow:0_2px_18px_oklch(0.15_0.03_260/0.55)]">Legible over any photo</h2>
      <p class="text-[15px]">Gradient scrim below, wave on top, text in the safe zone.</p>
    </div>
    <svg class="absolute inset-x-0 -bottom-px z-10 block w-full h-[clamp(56px,9vw,116px)] text-twd13-next [&_path]:fill-current pointer-events-none" viewBox="0 0 1440 120" preserveAspectRatio="none" aria-hidden="true" focusable="false">
      <path d="M0,62 C280,118 560,10 840,54 C1080,92 1250,106 1440,74 L1440,120 L0,120 Z"></path>
    </svg>
  </header>
  <div class="bg-twd13-next text-twd13-nextink px-6 pt-6 pb-15 text-center">
    <p class="text-[15px]">The wave fill matches this section exactly &mdash; no sticker effect.</p>
  </div>

  <!-- Wave-masked image band -->
  <div class="bg-twd13-paper text-twd13-paperink px-6 py-10 text-center">
    <p class="text-[15px]">Text section</p>
  </div>
  <div role="img" aria-label="Coastline photograph shown inside a wave-shaped band" class="-my-6 h-[clamp(220px,32vw,380px)] bg-twd13-bandfallback bg-[url('https://picsum.photos/seed/twd13b/1600/700')] bg-cover bg-center bg-no-repeat [mask:var(--twd13-band)_no-repeat_center/100%_100%]"></div>
  <div class="bg-twd13-paper text-twd13-paperink px-6 py-10 text-center">
    <p class="text-[15px] max-w-[52ch] mx-auto">The photo itself is masked into a wave band &mdash; both edges curved, no SVG node.</p>
  </div>

</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-twd13-next: #f6f7fa;
  --color-twd13-next: oklch(0.97 0.01 250);
  --color-twd13-nextink: #3f4655;
  --color-twd13-nextink: oklch(0.42 0.03 250);
  --color-twd13-eyebrow: #dfe4ee;
  --color-twd13-eyebrow: oklch(0.91 0.01 250);
  --color-twd13-paper: #f3f5f8;
  --color-twd13-paper: oklch(0.96 0.01 230);
  --color-twd13-paperink: #414a55;
  --color-twd13-paperink: oklch(0.43 0.03 230);
  --color-twd13-bandfallback: #35657f;
  --color-twd13-bandfallback: oklch(0.45 0.07 230);
  --font-twd13-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* The ribbon cutter: a wave on the top AND bottom edge of one mask. */

.twd-13 {
  width: 100%;
  min-height: 100svh;
  --twd13-band: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201440%20400'%20preserveAspectRatio='none'%3E%3Cpath%20d='M0,70%20C360,-10%20700,120%201000,70%20C1180,40%201300,40%201440,66%20L1440,330%20C1200,410%20900,270%20620,326%20C420,366%20180,366%200,334%20Z'%20fill='%23000'/%3E%3C/svg%3E");
}
/* Safari < 15.4 mask shim for the photo band. */

.twd-13 [role="img"] {
  -webkit-mask: var(--twd13-band) no-repeat center/100% 100%;
}
```

How this works

Three layers, in order: the image (object-cover with a focal object-position), a gradient scrim for contrast, then the wave on top filled with the next section's colour.

class="relative isolate overflow-hidden
        before:absolute before:inset-0 before:-z-10
        before:bg-[linear-gradient(180deg,#0a0e14/0.28,#0a0e14/0.8)]"

The scrim being a gradient is the whole trick: density where the copy and the wave live, transparency where the photograph is doing the work. The wave then reads as the page's edge rather than a sticker on the photo.

Variant b inverts the relationship — the photo is masked into a band with a wave on both edges, so the imagery itself is the divider.

Make it yours

  • Change the scrim stops to suit a bright or a dark photo; the bottom stop is the one that protects the type.
  • Move object-[50%_35%] to keep a subject's face clear of the curve.
  • Add a duotone layer with mix-blend-color for a branded photo treatment.
  • Swap the <img> for a muted looping <video> — the layer order is identical.

Gotchas — read before shipping

  • Test the headline against the darkest AND lightest parts of the photo, not the average. A scrim tuned to the average fails on the sky.
  • Decorative photos take alt=""; a meaningful one needs real alt text. The wave is always decorative.
  • Always give the image intrinsic width/height or the hero shifts when it lands — the CLS this whole collection avoids elsewhere.
  • Never put copy in the wave's deepest 40px; it gets clipped on small screens where the curve is proportionally taller.
  • isolate is required with negative z-index children. Without it, -z-20 puts the image behind the page background.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

object-fit and isolation are long-standing Baseline features; the masked-band variant needs mask-image (Safari 15.4+ with the shim), and falls back to a straight-edged photo band.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…