06 / 14
All 14 in 14 Tailwind CSS Wave Section Dividers
- 01Tailwind CSS SVG Wave Section Divider with Zero Dependencies
- 02Tailwind Animated Wave Section Divider for Landing Pages
- 03Tailwind Responsive SVG Wave Divider at the Bottom of a Hero
- 04Tailwind Stacked Multi Color SVG Wave Section Divider
- 05Tailwind Wave Section Divider Footer Transition
- 06Pure Tailwind Clip Path Wave Divider Without SVG
- 07Tailwind Inverted Curve SVG Wave Divider
- 08Tailwind Wave Divider Full Width Responsive Mobile Fix
- 09Tailwind Gradient Wave Section Divider with a Background Mask
- 10Tailwind Asymmetrical Organic SVG Section Wave
- 11Tailwind Dark Mode Wave Divider Colour Match
- 12Tailwind Animated Sine Wave Section Divider Transition
- 13Tailwind Wave Section Divider with an Overlay Background Image
- 14Tailwind Vertical Wave Divider for Side Navigation
Pure Tailwind Clip Path Wave Divider Without SVG
For engineers who refuse to ship inline SVG in their markup: three CSS-only cuts — clip-path: path(), a tiled scallop mask, and an elliptical border-radius dome — each expressed as a single arbitrary property, with no extra DOM weight at all.
Complete Tailwind CSS landing page templates in HTML, React, Next.js and Vue · from $49
Cruip · Affiliate link
The code
<section class="twd-06 w-full min-h-svh flex flex-col font-twd06-sans">
<!-- clip-path: path() wave -->
<div class="bg-twd06-forest text-white px-6 pt-16 pb-9 text-center">
<h2 class="text-[clamp(24px,3.8vw,36px)] font-semibold mb-2">No SVG in the DOM</h2>
<p class="text-[15px] text-twd06-onforest">The curve is one utility.</p>
</div>
<div class="bg-twd06-forest">
<div role="presentation" class="h-25 -mt-px bg-twd06-mint [clip-path:path('M0,44_C240,110_480,-6_720,38_C960,82_1200,110_1440,58_L5000,58_L5000,100_L0,100_Z')]"></div>
</div>
<div class="bg-twd06-mint text-twd06-forestink px-6 pt-3.5 pb-15 text-center">
<p class="text-[15px]"><code class="font-twd06-mono text-[13.5px]">clip-path: path()</code> takes SVG path syntax straight in CSS — in px, so this strip is fixed-height on purpose.</p>
</div>
<!-- Fluid scallop mask -->
<div class="bg-twd06-indigo text-white px-6 pt-16 pb-8 text-center">
<h2 class="text-[clamp(24px,3.8vw,36px)] font-semibold mb-2">Fluid without stretching</h2>
<p class="text-[15px] text-twd06-onindigo">The wave tiles instead of squashing.</p>
</div>
<div class="bg-twd06-indigo">
<div role="presentation" class="twd-06-scallop h-[42px] -mt-px bg-twd06-frost [mask:radial-gradient(calc(var(--twd06-pitch)/2)_at_50%_100%,transparent_98%,#000)_repeat-x_0_0/var(--twd06-pitch)_42px]"></div>
</div>
<div class="bg-twd06-frost text-twd06-indigoink px-6 pt-3 pb-15 text-center">
<p class="text-[15px] max-w-[52ch] mx-auto">A repeating radial-gradient mask keeps every crest the same size at any viewport.</p>
</div>
<!-- Ellipse dome via border-radius -->
<div class="overflow-hidden">
<div class="bg-twd06-rust text-white px-6 pt-16 pb-13 text-center">
<h2 class="text-[clamp(24px,3.8vw,36px)] font-semibold">One-line dome curve</h2>
</div>
<div class="relative w-[112%] -ml-[6%] -mt-10 bg-twd06-blush text-twd06-rustink px-6 pt-12 pb-15 text-center [border-radius:50%_50%_0_0/72px_72px_0_0]">
<p class="text-[15px] max-w-[52ch] mx-auto"><code class="font-twd06-mono text-[13.5px]">[border-radius:50%_50%_0_0/72px_72px_0_0]</code> — the cheapest curve in CSS.</p>
</div>
</div>
</section><section class="twd-06 w-full min-h-svh flex flex-col font-twd06-sans">
<!-- clip-path: path() wave -->
<div class="bg-twd06-forest text-white px-6 pt-16 pb-9 text-center">
<h2 class="text-[clamp(24px,3.8vw,36px)] font-semibold mb-2">No SVG in the DOM</h2>
<p class="text-[15px] text-twd06-onforest">The curve is one utility.</p>
</div>
<div class="bg-twd06-forest">
<div role="presentation" class="h-25 -mt-px bg-twd06-mint [clip-path:path('M0,44_C240,110_480,-6_720,38_C960,82_1200,110_1440,58_L5000,58_L5000,100_L0,100_Z')]"></div>
</div>
<div class="bg-twd06-mint text-twd06-forestink px-6 pt-3.5 pb-15 text-center">
<p class="text-[15px]"><code class="font-twd06-mono text-[13.5px]">clip-path: path()</code> takes SVG path syntax straight in CSS — in px, so this strip is fixed-height on purpose.</p>
</div>
<!-- Fluid scallop mask -->
<div class="bg-twd06-indigo text-white px-6 pt-16 pb-8 text-center">
<h2 class="text-[clamp(24px,3.8vw,36px)] font-semibold mb-2">Fluid without stretching</h2>
<p class="text-[15px] text-twd06-onindigo">The wave tiles instead of squashing.</p>
</div>
<div class="bg-twd06-indigo">
<div role="presentation" class="twd-06-scallop h-[42px] -mt-px bg-twd06-frost [mask:radial-gradient(calc(var(--twd06-pitch)/2)_at_50%_100%,transparent_98%,#000)_repeat-x_0_0/var(--twd06-pitch)_42px]"></div>
</div>
<div class="bg-twd06-frost text-twd06-indigoink px-6 pt-3 pb-15 text-center">
<p class="text-[15px] max-w-[52ch] mx-auto">A repeating radial-gradient mask keeps every crest the same size at any viewport.</p>
</div>
<!-- Ellipse dome via border-radius -->
<div class="overflow-hidden">
<div class="bg-twd06-rust text-white px-6 pt-16 pb-13 text-center">
<h2 class="text-[clamp(24px,3.8vw,36px)] font-semibold">One-line dome curve</h2>
</div>
<div class="relative w-[112%] -ml-[6%] -mt-10 bg-twd06-blush text-twd06-rustink px-6 pt-12 pb-15 text-center [border-radius:50%_50%_0_0/72px_72px_0_0]">
<p class="text-[15px] max-w-[52ch] mx-auto"><code class="font-twd06-mono text-[13.5px]">[border-radius:50%_50%_0_0/72px_72px_0_0]</code> — the cheapest curve in CSS.</p>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-twd06-forest: #1f513c;
--color-twd06-forest: oklch(0.38 0.08 158);
--color-twd06-onforest: #cfe4d9;
--color-twd06-onforest: oklch(0.89 0.03 158);
--color-twd06-mint: #f0f7f2;
--color-twd06-mint: oklch(0.96 0.02 150);
--color-twd06-forestink: #2f5a48;
--color-twd06-forestink: oklch(0.42 0.06 158);
--color-twd06-indigo: #453a8c;
--color-twd06-indigo: oklch(0.38 0.14 280);
--color-twd06-onindigo: #d6d2ee;
--color-twd06-onindigo: oklch(0.87 0.04 280);
--color-twd06-frost: #f6f6fc;
--color-twd06-frost: oklch(0.97 0.01 280);
--color-twd06-indigoink: #443c7e;
--color-twd06-indigoink: oklch(0.4 0.11 280);
--color-twd06-rust: #9a3b21;
--color-twd06-rust: oklch(0.47 0.15 38);
--color-twd06-blush: #fcf2ee;
--color-twd06-blush: oklch(0.97 0.02 38);
--color-twd06-rustink: #7a3a24;
--color-twd06-rustink: oklch(0.42 0.11 38);
--font-twd06-sans: 'Space Grotesk', system-ui, sans-serif;
--font-twd06-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Scallop pitch: a per-demo geometry knob, deliberately NOT an @theme entry —
nothing is gained by minting pitch-* utilities for one shape. */
.twd-06 {
width: 100%;
min-height: 100svh;
--twd06-pitch: 74px;
}
/* Safari < 15.4 mask shim. Scoped to the scallop strip by class, NOT to
[role="presentation"] — this demo has two such strips, and -webkit-mask is an alias of
mask in Chromium, so a broad selector would also mask the clip-path strip and the two
shapes would intersect into slivers. */
.twd-06 .twd-06-scallop {
-webkit-mask: radial-gradient(calc(var(--twd06-pitch)/2) at 50% 100%, transparent 98%, #000) repeat-x 0 0/var(--twd06-pitch) 42px;
}@import "tailwindcss";
@theme {
--color-twd06-forest: #1f513c;
--color-twd06-forest: oklch(0.38 0.08 158);
--color-twd06-onforest: #cfe4d9;
--color-twd06-onforest: oklch(0.89 0.03 158);
--color-twd06-mint: #f0f7f2;
--color-twd06-mint: oklch(0.96 0.02 150);
--color-twd06-forestink: #2f5a48;
--color-twd06-forestink: oklch(0.42 0.06 158);
--color-twd06-indigo: #453a8c;
--color-twd06-indigo: oklch(0.38 0.14 280);
--color-twd06-onindigo: #d6d2ee;
--color-twd06-onindigo: oklch(0.87 0.04 280);
--color-twd06-frost: #f6f6fc;
--color-twd06-frost: oklch(0.97 0.01 280);
--color-twd06-indigoink: #443c7e;
--color-twd06-indigoink: oklch(0.4 0.11 280);
--color-twd06-rust: #9a3b21;
--color-twd06-rust: oklch(0.47 0.15 38);
--color-twd06-blush: #fcf2ee;
--color-twd06-blush: oklch(0.97 0.02 38);
--color-twd06-rustink: #7a3a24;
--color-twd06-rustink: oklch(0.42 0.11 38);
--font-twd06-sans: 'Space Grotesk', system-ui, sans-serif;
--font-twd06-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Scallop pitch: a per-demo geometry knob, deliberately NOT an @theme entry —
nothing is gained by minting pitch-* utilities for one shape. */
.twd-06 {
width: 100%;
min-height: 100svh;
--twd06-pitch: 74px;
}
/* Safari < 15.4 mask shim. Scoped to the scallop strip by class, NOT to
[role="presentation"] — this demo has two such strips, and -webkit-mask is an alias of
mask in Chromium, so a broad selector would also mask the clip-path strip and the two
shapes would intersect into slivers. */
.twd-06 .twd-06-scallop {
-webkit-mask: radial-gradient(calc(var(--twd06-pitch)/2) at 50% 100%, transparent 98%, #000) repeat-x 0 0/var(--twd06-pitch) 42px;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<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.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-twd06-forest: #1f513c;
--color-twd06-forest: oklch(0.38 0.08 158);
--color-twd06-onforest: #cfe4d9;
--color-twd06-onforest: oklch(0.89 0.03 158);
--color-twd06-mint: #f0f7f2;
--color-twd06-mint: oklch(0.96 0.02 150);
--color-twd06-forestink: #2f5a48;
--color-twd06-forestink: oklch(0.42 0.06 158);
--color-twd06-indigo: #453a8c;
--color-twd06-indigo: oklch(0.38 0.14 280);
--color-twd06-onindigo: #d6d2ee;
--color-twd06-onindigo: oklch(0.87 0.04 280);
--color-twd06-frost: #f6f6fc;
--color-twd06-frost: oklch(0.97 0.01 280);
--color-twd06-indigoink: #443c7e;
--color-twd06-indigoink: oklch(0.4 0.11 280);
--color-twd06-rust: #9a3b21;
--color-twd06-rust: oklch(0.47 0.15 38);
--color-twd06-blush: #fcf2ee;
--color-twd06-blush: oklch(0.97 0.02 38);
--color-twd06-rustink: #7a3a24;
--color-twd06-rustink: oklch(0.42 0.11 38);
--font-twd06-sans: 'Space Grotesk', system-ui, sans-serif;
--font-twd06-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Scallop pitch: a per-demo geometry knob, deliberately NOT an @theme entry —
nothing is gained by minting pitch-* utilities for one shape. */
.twd-06 {
width: 100%;
min-height: 100svh;
--twd06-pitch: 74px;
}
/* Safari < 15.4 mask shim. Scoped to the scallop strip by class, NOT to
[role="presentation"] — this demo has two such strips, and -webkit-mask is an alias of
mask in Chromium, so a broad selector would also mask the clip-path strip and the two
shapes would intersect into slivers. */
.twd-06 .twd-06-scallop {
-webkit-mask: radial-gradient(calc(var(--twd06-pitch)/2) at 50% 100%, transparent 98%, #000) repeat-x 0 0/var(--twd06-pitch) 42px;
}@import "tailwindcss";
@theme {
--color-twd06-forest: #1f513c;
--color-twd06-forest: oklch(0.38 0.08 158);
--color-twd06-onforest: #cfe4d9;
--color-twd06-onforest: oklch(0.89 0.03 158);
--color-twd06-mint: #f0f7f2;
--color-twd06-mint: oklch(0.96 0.02 150);
--color-twd06-forestink: #2f5a48;
--color-twd06-forestink: oklch(0.42 0.06 158);
--color-twd06-indigo: #453a8c;
--color-twd06-indigo: oklch(0.38 0.14 280);
--color-twd06-onindigo: #d6d2ee;
--color-twd06-onindigo: oklch(0.87 0.04 280);
--color-twd06-frost: #f6f6fc;
--color-twd06-frost: oklch(0.97 0.01 280);
--color-twd06-indigoink: #443c7e;
--color-twd06-indigoink: oklch(0.4 0.11 280);
--color-twd06-rust: #9a3b21;
--color-twd06-rust: oklch(0.47 0.15 38);
--color-twd06-blush: #fcf2ee;
--color-twd06-blush: oklch(0.97 0.02 38);
--color-twd06-rustink: #7a3a24;
--color-twd06-rustink: oklch(0.42 0.11 38);
--font-twd06-sans: 'Space Grotesk', system-ui, sans-serif;
--font-twd06-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Scallop pitch: a per-demo geometry knob, deliberately NOT an @theme entry —
nothing is gained by minting pitch-* utilities for one shape. */
.twd-06 {
width: 100%;
min-height: 100svh;
--twd06-pitch: 74px;
}
/* Safari < 15.4 mask shim. Scoped to the scallop strip by class, NOT to
[role="presentation"] — this demo has two such strips, and -webkit-mask is an alias of
mask in Chromium, so a broad selector would also mask the clip-path strip and the two
shapes would intersect into slivers. */
.twd-06 .twd-06-scallop {
-webkit-mask: radial-gradient(calc(var(--twd06-pitch)/2) at 50% 100%, transparent 98%, #000) repeat-x 0 0/var(--twd06-pitch) 42px;
}<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&family=JetBrains+Mono:wght@400;500&display=swap">
<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&family=JetBrains+Mono:wght@400;500&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- This is the demo that justifies arbitrary properties.
clip-path, a fullmaskcomposition and a slash-syntaxborder-radiusare not design-scale values, and Tailwind is right not to ship utilities for them — the bracket syntax carries them through intact. - The
--twd06-pitchknob stays a plain custom property rather than an@themeentry, and both the mask utility and the CSS shim read it, so the two can never drift. - Underscore-for-space is the only friction: the
path()value is legible, but a longer path would be the point at which to move the shape into a custom property like 01 does. - The dome is the one place where reaching for a
rounded-*utility actively breaks the design —[border-radius:...]as an arbitrary property is the correct port of a slash-syntax radius, and the difference is invisible until you check the computed value. - The
-webkit-maskshim has to be scoped by class rather than by[role="presentation"]: this demo ships two cut strips, and because Chromium treats the prefixed property as an alias ofmask, a broad selector silently applied the scallop mask to the clip-path strip — the two shapes intersected and the wave vanished. A prefixed shim is CSS that utilities cannot see, so it needs the tightest selector you can give it. - UI change from the CSS original: the
path()runs its last edge out to x=5000 instead of closing at 1440. The original produced a notch of the preceding section on any display wider than 1440px — invisible on a laptop, obvious on a 27-inch monitor. - UI change from the CSS original: both cut strips now sit inside a parent carrying the PREVIOUS section's colour. A clip or a mask reveals whatever is behind the strip, and the original relied on the page being white — on any tinted page the original showed a white band above the curve instead of the section it was cutting away from.
- UI change from the CSS original: the dome variant's over-wide block (
width:112%; margin-left:-6%) is now wrapped in anoverflow-hiddenparent. The original relied on the demo root clipping it, which meant dropping it into any page with a visible overflow produced a horizontal scrollbar.
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: Pure Tailwind Clip Path Wave Divider Without SVG
Source: https://codefronts.com/layouts/tailwind-css-wave-section-dividers/pure-tailwind-clip-path-wave-divider-without-svg/
For engineers who refuse to ship inline SVG in their markup: three CSS-only cuts — clip-path: path(), a tiled scallop mask, and an elliptical border-radius dome — each expressed as a single arbitrary property, with no extra DOM weight at all.
## HTML
```html
<section class="twd-06 w-full min-h-svh flex flex-col font-twd06-sans">
<!-- clip-path: path() wave -->
<div class="bg-twd06-forest text-white px-6 pt-16 pb-9 text-center">
<h2 class="text-[clamp(24px,3.8vw,36px)] font-semibold mb-2">No SVG in the DOM</h2>
<p class="text-[15px] text-twd06-onforest">The curve is one utility.</p>
</div>
<div class="bg-twd06-forest">
<div role="presentation" class="h-25 -mt-px bg-twd06-mint [clip-path:path('M0,44_C240,110_480,-6_720,38_C960,82_1200,110_1440,58_L5000,58_L5000,100_L0,100_Z')]"></div>
</div>
<div class="bg-twd06-mint text-twd06-forestink px-6 pt-3.5 pb-15 text-center">
<p class="text-[15px]"><code class="font-twd06-mono text-[13.5px]">clip-path: path()</code> takes SVG path syntax straight in CSS — in px, so this strip is fixed-height on purpose.</p>
</div>
<!-- Fluid scallop mask -->
<div class="bg-twd06-indigo text-white px-6 pt-16 pb-8 text-center">
<h2 class="text-[clamp(24px,3.8vw,36px)] font-semibold mb-2">Fluid without stretching</h2>
<p class="text-[15px] text-twd06-onindigo">The wave tiles instead of squashing.</p>
</div>
<div class="bg-twd06-indigo">
<div role="presentation" class="twd-06-scallop h-[42px] -mt-px bg-twd06-frost [mask:radial-gradient(calc(var(--twd06-pitch)/2)_at_50%_100%,transparent_98%,#000)_repeat-x_0_0/var(--twd06-pitch)_42px]"></div>
</div>
<div class="bg-twd06-frost text-twd06-indigoink px-6 pt-3 pb-15 text-center">
<p class="text-[15px] max-w-[52ch] mx-auto">A repeating radial-gradient mask keeps every crest the same size at any viewport.</p>
</div>
<!-- Ellipse dome via border-radius -->
<div class="overflow-hidden">
<div class="bg-twd06-rust text-white px-6 pt-16 pb-13 text-center">
<h2 class="text-[clamp(24px,3.8vw,36px)] font-semibold">One-line dome curve</h2>
</div>
<div class="relative w-[112%] -ml-[6%] -mt-10 bg-twd06-blush text-twd06-rustink px-6 pt-12 pb-15 text-center [border-radius:50%_50%_0_0/72px_72px_0_0]">
<p class="text-[15px] max-w-[52ch] mx-auto"><code class="font-twd06-mono text-[13.5px]">[border-radius:50%_50%_0_0/72px_72px_0_0]</code> — the cheapest curve in CSS.</p>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-twd06-forest: #1f513c;
--color-twd06-forest: oklch(0.38 0.08 158);
--color-twd06-onforest: #cfe4d9;
--color-twd06-onforest: oklch(0.89 0.03 158);
--color-twd06-mint: #f0f7f2;
--color-twd06-mint: oklch(0.96 0.02 150);
--color-twd06-forestink: #2f5a48;
--color-twd06-forestink: oklch(0.42 0.06 158);
--color-twd06-indigo: #453a8c;
--color-twd06-indigo: oklch(0.38 0.14 280);
--color-twd06-onindigo: #d6d2ee;
--color-twd06-onindigo: oklch(0.87 0.04 280);
--color-twd06-frost: #f6f6fc;
--color-twd06-frost: oklch(0.97 0.01 280);
--color-twd06-indigoink: #443c7e;
--color-twd06-indigoink: oklch(0.4 0.11 280);
--color-twd06-rust: #9a3b21;
--color-twd06-rust: oklch(0.47 0.15 38);
--color-twd06-blush: #fcf2ee;
--color-twd06-blush: oklch(0.97 0.02 38);
--color-twd06-rustink: #7a3a24;
--color-twd06-rustink: oklch(0.42 0.11 38);
--font-twd06-sans: 'Space Grotesk', system-ui, sans-serif;
--font-twd06-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Scallop pitch: a per-demo geometry knob, deliberately NOT an @theme entry —
nothing is gained by minting pitch-* utilities for one shape. */
.twd-06 {
width: 100%;
min-height: 100svh;
--twd06-pitch: 74px;
}
/* Safari < 15.4 mask shim. Scoped to the scallop strip by class, NOT to
[role="presentation"] — this demo has two such strips, and -webkit-mask is an alias of
mask in Chromium, so a broad selector would also mask the clip-path strip and the two
shapes would intersect into slivers. */
.twd-06 .twd-06-scallop {
-webkit-mask: radial-gradient(calc(var(--twd06-pitch)/2) at 50% 100%, transparent 98%, #000) repeat-x 0 0/var(--twd06-pitch) 42px;
}
```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: Pure Tailwind Clip Path Wave Divider Without SVG
Source: https://codefronts.com/layouts/tailwind-css-wave-section-dividers/pure-tailwind-clip-path-wave-divider-without-svg/
For engineers who refuse to ship inline SVG in their markup: three CSS-only cuts — clip-path: path(), a tiled scallop mask, and an elliptical border-radius dome — each expressed as a single arbitrary property, with no extra DOM weight at all.
## HTML
```html
<section class="twd-06 w-full min-h-svh flex flex-col font-twd06-sans">
<!-- clip-path: path() wave -->
<div class="bg-twd06-forest text-white px-6 pt-16 pb-9 text-center">
<h2 class="text-[clamp(24px,3.8vw,36px)] font-semibold mb-2">No SVG in the DOM</h2>
<p class="text-[15px] text-twd06-onforest">The curve is one utility.</p>
</div>
<div class="bg-twd06-forest">
<div role="presentation" class="h-25 -mt-px bg-twd06-mint [clip-path:path('M0,44_C240,110_480,-6_720,38_C960,82_1200,110_1440,58_L5000,58_L5000,100_L0,100_Z')]"></div>
</div>
<div class="bg-twd06-mint text-twd06-forestink px-6 pt-3.5 pb-15 text-center">
<p class="text-[15px]"><code class="font-twd06-mono text-[13.5px]">clip-path: path()</code> takes SVG path syntax straight in CSS — in px, so this strip is fixed-height on purpose.</p>
</div>
<!-- Fluid scallop mask -->
<div class="bg-twd06-indigo text-white px-6 pt-16 pb-8 text-center">
<h2 class="text-[clamp(24px,3.8vw,36px)] font-semibold mb-2">Fluid without stretching</h2>
<p class="text-[15px] text-twd06-onindigo">The wave tiles instead of squashing.</p>
</div>
<div class="bg-twd06-indigo">
<div role="presentation" class="twd-06-scallop h-[42px] -mt-px bg-twd06-frost [mask:radial-gradient(calc(var(--twd06-pitch)/2)_at_50%_100%,transparent_98%,#000)_repeat-x_0_0/var(--twd06-pitch)_42px]"></div>
</div>
<div class="bg-twd06-frost text-twd06-indigoink px-6 pt-3 pb-15 text-center">
<p class="text-[15px] max-w-[52ch] mx-auto">A repeating radial-gradient mask keeps every crest the same size at any viewport.</p>
</div>
<!-- Ellipse dome via border-radius -->
<div class="overflow-hidden">
<div class="bg-twd06-rust text-white px-6 pt-16 pb-13 text-center">
<h2 class="text-[clamp(24px,3.8vw,36px)] font-semibold">One-line dome curve</h2>
</div>
<div class="relative w-[112%] -ml-[6%] -mt-10 bg-twd06-blush text-twd06-rustink px-6 pt-12 pb-15 text-center [border-radius:50%_50%_0_0/72px_72px_0_0]">
<p class="text-[15px] max-w-[52ch] mx-auto"><code class="font-twd06-mono text-[13.5px]">[border-radius:50%_50%_0_0/72px_72px_0_0]</code> — the cheapest curve in CSS.</p>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-twd06-forest: #1f513c;
--color-twd06-forest: oklch(0.38 0.08 158);
--color-twd06-onforest: #cfe4d9;
--color-twd06-onforest: oklch(0.89 0.03 158);
--color-twd06-mint: #f0f7f2;
--color-twd06-mint: oklch(0.96 0.02 150);
--color-twd06-forestink: #2f5a48;
--color-twd06-forestink: oklch(0.42 0.06 158);
--color-twd06-indigo: #453a8c;
--color-twd06-indigo: oklch(0.38 0.14 280);
--color-twd06-onindigo: #d6d2ee;
--color-twd06-onindigo: oklch(0.87 0.04 280);
--color-twd06-frost: #f6f6fc;
--color-twd06-frost: oklch(0.97 0.01 280);
--color-twd06-indigoink: #443c7e;
--color-twd06-indigoink: oklch(0.4 0.11 280);
--color-twd06-rust: #9a3b21;
--color-twd06-rust: oklch(0.47 0.15 38);
--color-twd06-blush: #fcf2ee;
--color-twd06-blush: oklch(0.97 0.02 38);
--color-twd06-rustink: #7a3a24;
--color-twd06-rustink: oklch(0.42 0.11 38);
--font-twd06-sans: 'Space Grotesk', system-ui, sans-serif;
--font-twd06-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Scallop pitch: a per-demo geometry knob, deliberately NOT an @theme entry —
nothing is gained by minting pitch-* utilities for one shape. */
.twd-06 {
width: 100%;
min-height: 100svh;
--twd06-pitch: 74px;
}
/* Safari < 15.4 mask shim. Scoped to the scallop strip by class, NOT to
[role="presentation"] — this demo has two such strips, and -webkit-mask is an alias of
mask in Chromium, so a broad selector would also mask the clip-path strip and the two
shapes would intersect into slivers. */
.twd-06 .twd-06-scallop {
-webkit-mask: radial-gradient(calc(var(--twd06-pitch)/2) at 50% 100%, transparent 98%, #000) repeat-x 0 0/var(--twd06-pitch) 42px;
}
```How this works
clip-path: path() takes the same path syntax as SVG, but its coordinates are pixels, not a viewBox — so it does not stretch. Pair it with a fixed-height strip and accept that, or use the mask instead.
class="h-25 bg-twd06-b -mt-px
[clip-path:path('M0,44_C240,110_480,-6_720,38_C960,82_1200,110_1440,58_L5000,58_L5000,100_L0,100_Z')]"The L5000 pair is not decoration: a path() is only defined where its data goes, so on a display wider than the authored 1440px everything past that x falls outside the clip and the strip shows a notch of the section behind it. A flat run-out to a width no display reaches closes it.
The tiled mask is the fluid one. A radial-gradient repeating at a fixed pitch keeps every crest the same size, so a wider screen gets more crests instead of wider ones. The pitch lives in a --twd06-pitch custom property and is read by both the gradient radius and the tile size.
The dome needs nothing at all — an over-wide block with an elliptical border-radius, pulled up over the previous section. No mask, no clip, no SVG, and support back to IE9. Note that it has to be written as an arbitrary property: rounded-t-[…] expands to the two top-corner longhands, and a four-value slash shorthand is invalid there, so the browser drops it silently.
Make it yours
- Scallop pitch: one number,
--twd06-pitch, drives both the radius and the tile. - Swap
[clip-path:ellipse(100%_60%_at_50%_100%)]for a single soft dome with no path at all. - Dome depth: the
72pxpair in[border-radius:50%_50%_0_0/72px_72px_0_0], paired with the block's-mt-10. - Clip the section itself rather than a strip to drop one element — but see the gotcha.
- Animate between two same-command paths for a morphing edge (
transition-[clip-path]).
Gotchas — read before shipping
path()does not scale. This variant is intentionally fixed-height; treating it as fluid is the bug report.- End the path at the authored width and every viewport wider than that gets an unclipped notch on the right. Either run the last edge out past any plausible display width (as here,
L5000) or cap the strip withmax-w-[1440px] mx-auto. - Clipping a section clips its shadow and any absolutely positioned children too — including a sticky header inside it.
- Ship both
-webkit-maskandmask. The prefix has to live in CSS; there is no variant for it. - Overlap with
-mt-pxor anti-aliasing reveals a hairline of the block above. - In arbitrary values, underscores become spaces. A path with a genuine underscore in it (there are none here) would need
\_. - The dome's curve must be an arbitrary property, not
rounded-t-[…]: that utility compiles toborder-top-left-radius+border-top-right-radius, and the slash shorthand is invalid for those longhands, so both declarations are dropped and the dome silently renders as a rectangle. Per-cornerrounded-tl-[50%/72px] rounded-tr-[50%/72px]is the utility-shaped alternative.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
mask-image is the most portable of the three; clip-path:path() needs Chrome 88+/Safari 13.1+. Without support the strip renders as a straight-edged band — graceful, not broken.