14 CSS Wave Section Dividers08 / 14

CSS + JSMIT licensed

CSS wave divider full width responsive mobile fix

The bug-fix demo: side-by-side broken and fixed dividers so you can see exactly what causes the white gap, the hairline seam and the horizontally smeared curve on narrow screens — plus a live viewport squeeze slider that reproduces the failure without resizing your browser.

Published

Live Demo
Try it

The code

<div class="wsd-08-group">
<section class="wsd-08a">
  <div class="wsd-08a__ctl">
    <label class="wsd-08a__label" for="wsd-08a-range">Simulated viewport width: <output id="wsd-08a-out">100%</output></label>
    <input class="wsd-08a__range" id="wsd-08a-range" type="range" min="32" max="100" value="100" step="1" aria-describedby="wsd-08a-hint">
    <p class="wsd-08a__hint" id="wsd-08a-hint">Drag to squeeze both panes. The left pane shows the classic bugs; the right pane is the fixed recipe.</p>
  </div>
  <div class="wsd-08a__panes" id="wsd-08a-panes">
    <div class="wsd-08a__pane">
      <p class="wsd-08a__tag wsd-08a__tag--bad">Broken</p>
      <div class="wsd-08a__demo wsd-08a__demo--bad">
        <div class="wsd-08a__hero"><span>Hero</span>
          <svg class="wsd-08a__wave" viewBox="0 0 1440 120" preserveAspectRatio="none" aria-hidden="true" focusable="false"><path d="M0,60 C300,120 620,0 940,54 C1160,90 1300,100 1440,70 L1440,120 L0,120 Z"></path></svg>
        </div>
        <div class="wsd-08a__body"><span>Next section</span></div>
      </div>
      <ul class="wsd-08a__notes"><li>inline SVG baseline gap</li><li>seam at fractional DPR</li><li>curve smears when narrow</li></ul>
    </div>
    <div class="wsd-08a__pane">
      <p class="wsd-08a__tag wsd-08a__tag--good">Fixed</p>
      <div class="wsd-08a__demo wsd-08a__demo--good">
        <div class="wsd-08a__hero"><span>Hero</span>
          <svg class="wsd-08a__wave" viewBox="0 0 1440 120" preserveAspectRatio="none" aria-hidden="true" focusable="false"><path d="M0,60 C300,120 620,0 940,54 C1160,90 1300,100 1440,70 L1440,120 L0,120 Z"></path></svg>
        </div>
        <div class="wsd-08a__body"><span>Next section</span></div>
      </div>
      <ul class="wsd-08a__notes"><li>display:block</li><li>bottom:-1px overlap</li><li>min-width keeps the ratio</li></ul>
    </div>
  </div>
</section>
<section class="wsd-08b">
  <div class="wsd-08b__hero">
    <h2 class="wsd-08b__title">Different wave per breakpoint</h2>
    <p class="wsd-08b__copy">Below 640px a taller, shorter-wavelength mask takes over so the curve stays legible.</p>
  </div>
  <div class="wsd-08b__divider" role="presentation"></div>
  <div class="wsd-08b__body"><p class="wsd-08b__copy">Swapping the mask beats stretching one path across every viewport.</p></div>
</section>
</div>
.wsd-08-group {
  width: 100%;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.wsd-08-group > section {
  width: 100%;
}

.wsd-08a,
.wsd-08a *,
.wsd-08a *::before,
.wsd-08a *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.wsd-08a {
  --ink: oklch(0.24 0.02 250);
  --paper: oklch(0.97 0.005 250);
  --bad: oklch(0.62 0.2 22);
  --good: oklch(0.68 0.16 155);
  font-family: 'Segoe UI',system-ui,sans-serif;
  background: var(--paper);
  padding: 26px 20px 34px;
  color: var(--ink);
}

.wsd-08a__ctl {
  max-width: 720px;
  margin: 0 auto 22px;
}

.wsd-08a__label {
  display: block;
  font-size: 13px;
  font-weight: 700;
  margin-bottom: 8px;
}

.wsd-08a__label output {
  font-family: ui-monospace,monospace;
  font-weight: 800;
}

.wsd-08a__range {
  width: 100%;
  accent-color: oklch(0.55 0.16 260);
}

.wsd-08a__range:focus-visible {
  outline: 3px solid oklch(0.55 0.16 260);
  outline-offset: 4px;
}

.wsd-08a__hint {
  font-size: 12.5px;
  opacity: .66;
  margin-top: 8px;
}

.wsd-08a__panes {
  display: grid;
  grid-template-columns: repeat(auto-fit,minmax(260px,1fr));
  gap: 20px;
  max-width: 920px;
  margin: 0 auto;
}

.wsd-08a__pane {
  min-width: 0;
}

.wsd-08a__tag {
  display: inline-block;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .12em;
  text-transform: uppercase;
  padding: 4px 9px;
  border-radius: 999px;
  margin-bottom: 9px;
  color: #fff;
}

.wsd-08a__tag--bad {
  background: var(--bad);
}

.wsd-08a__tag--good {
  background: var(--good);
}

.wsd-08a__demo {
  border: 1px solid oklch(0.88 0.01 250);
  border-radius: 12px;
  overflow: hidden;
}

.wsd-08a__hero {
  position: relative;
  background: var(--ink);
  color: #fff;
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 30px 12px 54px;
  text-align: center;
}

.wsd-08a__body {
  background: oklch(0.86 0.03 250);
  color: var(--ink);
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 20px 12px;
  text-align: center;
}

.wsd-08a__demo--bad .wsd-08a__wave {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 46px;
  color: oklch(0.86 0.03 250);
}

.wsd-08a__demo--good .wsd-08a__wave {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: -1px;
  display: block;
  width: 100%;
  min-width: 520px;
  height: clamp(38px,6vw,52px);
  color: oklch(0.86 0.03 250);
  pointer-events: none;
}

.wsd-08a__wave path {
  fill: currentColor;
}

.wsd-08a__demo--good {
  overflow-x: clip;
}

.wsd-08a__notes {
  list-style: none;
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 12.5px;
  font-family: ui-monospace,monospace;
  opacity: .72;
}

.wsd-08a__notes li::before {
  content: "› ";
  opacity: .5;
}

.wsd-08b,
.wsd-08b *,
.wsd-08b *::before,
.wsd-08b *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.wsd-08b {
  --hero: oklch(0.3 0.1 300);
  --next: oklch(0.96 0.02 300);
  --wave: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 110' preserveAspectRatio='none'%3E%3Cpath d='M0,54 C300,110 620,4 940,52 C1160,86 1300,96 1440,66 L1440,110 L0,110 Z' fill='%23000'/%3E%3C/svg%3E");
  font-family: 'Segoe UI',system-ui,sans-serif;
}

.wsd-08b__hero {
  background: var(--hero);
  color: #fff;
  padding: 62px 24px 34px;
  text-align: center;
}

.wsd-08b__title {
  font-size: clamp(23px,3.6vw,34px);
  margin-bottom: 8px;
}

.wsd-08b__copy {
  font-size: 15px;
  opacity: .8;
  max-width: 50ch;
  margin: 0 auto;
}

.wsd-08b__divider {
  height: clamp(52px,8vw,100px);
  background: var(--next);
  -webkit-mask: var(--wave) no-repeat center/100% 100%;
  mask: var(--wave) no-repeat center/100% 100%;
  margin-top: -1px;
}

.wsd-08b__body {
  background: var(--next);
  color: oklch(0.32 0.07 300);
  padding: 10px 24px 58px;
  text-align: center;
}

.wsd-08b__body .wsd-08b__copy {
  opacity: .74;
}

@media (max-width: 640px) {
  .wsd-08b {
    --wave: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 480 110' preserveAspectRatio='none'%3E%3Cpath d='M0,48 C80,104 160,6 240,48 C320,90 400,104 480,60 L480,110 L0,110 Z' fill='%23000'/%3E%3C/svg%3E");
  }
}
(() => {
  const range = document.querySelector('#wsd-08a-range');
  const panes = document.querySelector('#wsd-08a-panes');
  const out = document.querySelector('#wsd-08a-out');
  if (!range || !panes || !out) return;
  const apply = () => { panes.style.width = range.value + '%'; panes.style.marginInline = 'auto'; out.textContent = range.value + '%'; };
  range.addEventListener('input', apply); apply();
})();

/* No JavaScript — the wave lives in a --wave custom property, so a media query can swap the entire shape without touching markup. */
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 Wave Section Divider 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: CSS wave divider full width responsive mobile fix
Source: https://codefronts.com/layouts/css-wave-section-dividers/css-wave-divider-full-width-responsive-mobile-fix/

The bug-fix demo: side-by-side broken and fixed dividers so you can see exactly what causes the white gap, the hairline seam and the horizontally smeared curve on narrow screens — plus a live viewport squeeze slider that reproduces the failure without resizing your browser.
## HTML
```html
<div class="wsd-08-group">
<section class="wsd-08a">
  <div class="wsd-08a__ctl">
    <label class="wsd-08a__label" for="wsd-08a-range">Simulated viewport width: <output id="wsd-08a-out">100%</output></label>
    <input class="wsd-08a__range" id="wsd-08a-range" type="range" min="32" max="100" value="100" step="1" aria-describedby="wsd-08a-hint">
    <p class="wsd-08a__hint" id="wsd-08a-hint">Drag to squeeze both panes. The left pane shows the classic bugs; the right pane is the fixed recipe.</p>
  </div>
  <div class="wsd-08a__panes" id="wsd-08a-panes">
    <div class="wsd-08a__pane">
      <p class="wsd-08a__tag wsd-08a__tag--bad">Broken</p>
      <div class="wsd-08a__demo wsd-08a__demo--bad">
        <div class="wsd-08a__hero"><span>Hero</span>
          <svg class="wsd-08a__wave" viewBox="0 0 1440 120" preserveAspectRatio="none" aria-hidden="true" focusable="false"><path d="M0,60 C300,120 620,0 940,54 C1160,90 1300,100 1440,70 L1440,120 L0,120 Z"></path></svg>
        </div>
        <div class="wsd-08a__body"><span>Next section</span></div>
      </div>
      <ul class="wsd-08a__notes"><li>inline SVG baseline gap</li><li>seam at fractional DPR</li><li>curve smears when narrow</li></ul>
    </div>
    <div class="wsd-08a__pane">
      <p class="wsd-08a__tag wsd-08a__tag--good">Fixed</p>
      <div class="wsd-08a__demo wsd-08a__demo--good">
        <div class="wsd-08a__hero"><span>Hero</span>
          <svg class="wsd-08a__wave" viewBox="0 0 1440 120" preserveAspectRatio="none" aria-hidden="true" focusable="false"><path d="M0,60 C300,120 620,0 940,54 C1160,90 1300,100 1440,70 L1440,120 L0,120 Z"></path></svg>
        </div>
        <div class="wsd-08a__body"><span>Next section</span></div>
      </div>
      <ul class="wsd-08a__notes"><li>display:block</li><li>bottom:-1px overlap</li><li>min-width keeps the ratio</li></ul>
    </div>
  </div>
</section>
<section class="wsd-08b">
  <div class="wsd-08b__hero">
    <h2 class="wsd-08b__title">Different wave per breakpoint</h2>
    <p class="wsd-08b__copy">Below 640px a taller, shorter-wavelength mask takes over so the curve stays legible.</p>
  </div>
  <div class="wsd-08b__divider" role="presentation"></div>
  <div class="wsd-08b__body"><p class="wsd-08b__copy">Swapping the mask beats stretching one path across every viewport.</p></div>
</section>
</div>
```
## CSS
```css
.wsd-08-group {
  width: 100%;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.wsd-08-group > section {
  width: 100%;
}

.wsd-08a,
.wsd-08a *,
.wsd-08a *::before,
.wsd-08a *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.wsd-08a {
  --ink: oklch(0.24 0.02 250);
  --paper: oklch(0.97 0.005 250);
  --bad: oklch(0.62 0.2 22);
  --good: oklch(0.68 0.16 155);
  font-family: 'Segoe UI',system-ui,sans-serif;
  background: var(--paper);
  padding: 26px 20px 34px;
  color: var(--ink);
}

.wsd-08a__ctl {
  max-width: 720px;
  margin: 0 auto 22px;
}

.wsd-08a__label {
  display: block;
  font-size: 13px;
  font-weight: 700;
  margin-bottom: 8px;
}

.wsd-08a__label output {
  font-family: ui-monospace,monospace;
  font-weight: 800;
}

.wsd-08a__range {
  width: 100%;
  accent-color: oklch(0.55 0.16 260);
}

.wsd-08a__range:focus-visible {
  outline: 3px solid oklch(0.55 0.16 260);
  outline-offset: 4px;
}

.wsd-08a__hint {
  font-size: 12.5px;
  opacity: .66;
  margin-top: 8px;
}

.wsd-08a__panes {
  display: grid;
  grid-template-columns: repeat(auto-fit,minmax(260px,1fr));
  gap: 20px;
  max-width: 920px;
  margin: 0 auto;
}

.wsd-08a__pane {
  min-width: 0;
}

.wsd-08a__tag {
  display: inline-block;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .12em;
  text-transform: uppercase;
  padding: 4px 9px;
  border-radius: 999px;
  margin-bottom: 9px;
  color: #fff;
}

.wsd-08a__tag--bad {
  background: var(--bad);
}

.wsd-08a__tag--good {
  background: var(--good);
}

.wsd-08a__demo {
  border: 1px solid oklch(0.88 0.01 250);
  border-radius: 12px;
  overflow: hidden;
}

.wsd-08a__hero {
  position: relative;
  background: var(--ink);
  color: #fff;
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 30px 12px 54px;
  text-align: center;
}

.wsd-08a__body {
  background: oklch(0.86 0.03 250);
  color: var(--ink);
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 20px 12px;
  text-align: center;
}

.wsd-08a__demo--bad .wsd-08a__wave {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 46px;
  color: oklch(0.86 0.03 250);
}

.wsd-08a__demo--good .wsd-08a__wave {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: -1px;
  display: block;
  width: 100%;
  min-width: 520px;
  height: clamp(38px,6vw,52px);
  color: oklch(0.86 0.03 250);
  pointer-events: none;
}

.wsd-08a__wave path {
  fill: currentColor;
}

.wsd-08a__demo--good {
  overflow-x: clip;
}

.wsd-08a__notes {
  list-style: none;
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 12.5px;
  font-family: ui-monospace,monospace;
  opacity: .72;
}

.wsd-08a__notes li::before {
  content: "› ";
  opacity: .5;
}

.wsd-08b,
.wsd-08b *,
.wsd-08b *::before,
.wsd-08b *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.wsd-08b {
  --hero: oklch(0.3 0.1 300);
  --next: oklch(0.96 0.02 300);
  --wave: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 110' preserveAspectRatio='none'%3E%3Cpath d='M0,54 C300,110 620,4 940,52 C1160,86 1300,96 1440,66 L1440,110 L0,110 Z' fill='%23000'/%3E%3C/svg%3E");
  font-family: 'Segoe UI',system-ui,sans-serif;
}

.wsd-08b__hero {
  background: var(--hero);
  color: #fff;
  padding: 62px 24px 34px;
  text-align: center;
}

.wsd-08b__title {
  font-size: clamp(23px,3.6vw,34px);
  margin-bottom: 8px;
}

.wsd-08b__copy {
  font-size: 15px;
  opacity: .8;
  max-width: 50ch;
  margin: 0 auto;
}

.wsd-08b__divider {
  height: clamp(52px,8vw,100px);
  background: var(--next);
  -webkit-mask: var(--wave) no-repeat center/100% 100%;
  mask: var(--wave) no-repeat center/100% 100%;
  margin-top: -1px;
}

.wsd-08b__body {
  background: var(--next);
  color: oklch(0.32 0.07 300);
  padding: 10px 24px 58px;
  text-align: center;
}

.wsd-08b__body .wsd-08b__copy {
  opacity: .74;
}

@media (max-width: 640px) {
  .wsd-08b {
    --wave: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 480 110' preserveAspectRatio='none'%3E%3Cpath d='M0,48 C80,104 160,6 240,48 C320,90 400,104 480,60 L480,110 L0,110 Z' fill='%23000'/%3E%3C/svg%3E");
  }
}
```

## JavaScript
```js
(() => {
  const range = document.querySelector('#wsd-08a-range');
  const panes = document.querySelector('#wsd-08a-panes');
  const out = document.querySelector('#wsd-08a-out');
  if (!range || !panes || !out) return;
  const apply = () => { panes.style.width = range.value + '%'; panes.style.marginInline = 'auto'; out.textContent = range.value + '%'; };
  range.addEventListener('input', apply); apply();
})();

/* No JavaScript — the wave lives in a --wave custom property, so a media query can swap the entire shape without touching markup. */
```

How this works

Four failures cause 95% of reported wave bugs:

  1. Inline baseline gap — an SVG is an inline element, so it sits on a text baseline and leaves ~4px of the parent's background below it. Fix: display:block.
  2. Hairline seam — fractional device pixel ratios round the wave's bottom edge up. Fix: bottom:-1px (or margin-bottom:-1px).
  3. Smeared curve — the wave is stretched to a 5:1 ratio on desktop and squashed on mobile. Fix: clamp the height, or use a min-width so the wave keeps its ratio and simply overflows.
  4. Horizontal scrollbarwidth:100vw includes the scrollbar. Fix: width:100% and overflow-x:clip on the wrapper.
/* the durable recipe */
.wave{display:block;width:100%;min-width:640px;height:clamp(48px,8vw,110px);bottom:-1px}
.section{position:relative;overflow-x:clip}

min-width is the underrated one: below 640px the wave keeps its authored proportions and the excess simply overflows the clipped section, so the curve stays crisp instead of flattening.

Make it yours

  • Raise min-width to 900px for a very gentle, barely-curved mobile edge.
  • Anchor the overflow with left:50%;translateX(-50%) to keep the crest centred.
  • Swap in a shorter, higher-amplitude path via a media query for phones.
  • Use the slider to find the exact breakpoint where your path starts to look wrong.

Gotchas — read before shipping

  • Never use 100vw on the wave or you create a horizontal scrollbar on Windows.
  • overflow:hidden on an ancestor kills sticky positioning — prefer overflow-x:clip.
  • Do not fix seams with a 1px border; borders scale differently at high DPR.
  • Test at DPR 1.5 and 2.25 — the seam usually only shows at fractional ratios.

Browser support

ChromeSafariFirefoxEdge
111+15.4+113+111+

Floor set by oklch() as this demo is written. The core technique itself goes back further — Chrome 90+ (overflow:clip).

overflow-x:clip is the only recent feature; overflow-x:hidden is a safe fallback everywhere else.

Techniques used in this demo

Search CodeFronts

Loading…