Copied!
Band

Zigzag section divider

Two full-width bands joined by a wide, flat, uniform zigzag edge drawn with one stretched SVG polygon.

← Library

Element · Vanilla JS + SVGUpdated November 6, 2026

BandElementVanilla JS + SVG

Live preview needs JavaScript. The code below works without any dependencies.

Prompt for your AI assistant

Paste this into Claude, Cursor or ChatGPT. The settings update as you move the controls above.

Build "Zigzag section divider" for my site using the Enrichr Library element at https://enrichr.dev/elements/zigzag-section-divider.html.

Read the machine-readable spec and source at https://enrichr.dev/elements/zigzag-section-divider.json (markup, CSS, JavaScript, parameters).
Requirements: vanilla JavaScript and CSS, no new dependencies; keep the behaviour of the source unchanged; respect prefers-reduced-motion; work on touch screens; keep it accessible.

Settings:
- topTitle: Build the top half
- topText: A full-width band, joined to the next one by a wide zigzag edge.
- bottomTitle: Then the bottom half
- bottomText: The second band picks up where the teeth leave off.
- teeth: 5
- edge: 40
- top: #005236
- bottom: #e6ff55
- light: #fbf8f1

Verify: reduced motion shows no movement, it works at 375px wide, there are no console errors, and scrolling stays smooth.

The same spec as JSON, for tools that read it directly: /elements/zigzag-section-divider.json.

Code

Mount it on an element that contains the markup. It returns an object with update(options) and destroy().

HTML

<div class="ef-zsd">
  <section class="ef-zsd__top">
    <div class="ef-zsd__in">
      <h2 class="ef-zsd__h ef-zsd__th">Build the top half</h2>
      <p class="ef-zsd__p ef-zsd__tp">A full-width band, joined to the next one by a wide zigzag edge.</p>
    </div>
  </section>
  <div class="ef-zsd__edge">
    <svg class="ef-zsd__svg" viewBox="0 0 1000 40" preserveAspectRatio="none" aria-hidden="true" focusable="false"><polygon class="ef-zsd__poly" points="0,40 100,0 200,40 300,0 400,40 500,0 600,40 700,0 800,40 900,0 1000,40 1000,0 0,0"/></svg>
  </div>
  <section class="ef-zsd__bot">
    <div class="ef-zsd__in">
      <h2 class="ef-zsd__h ef-zsd__bh">Then the bottom half</h2>
      <p class="ef-zsd__p ef-zsd__bp">The second band picks up where the teeth leave off.</p>
    </div>
  </section>
</div>

CSS

/* Enrichr Library · https://enrichr.dev */
.ef-zsd { --ef-zsd-top: #005236; --ef-zsd-bot: #e6ff55; --ef-zsd-light: #fbf8f1; --ef-zsd-edge: 40px; width: 100%; box-sizing: border-box; overflow: hidden; border-radius: 1rem; background: var(--ef-zsd-bot); container-type: inline-size; font-family: 'Instrument Sans', system-ui, sans-serif; }
.ef-zsd .ef-zsd__top, .ef-zsd .ef-zsd__bot { width: 100%; box-sizing: border-box; padding: 6rem 1.5rem; text-align: center; }
.ef-zsd .ef-zsd__top { background: var(--ef-zsd-top); color: var(--ef-zsd-light); }
.ef-zsd .ef-zsd__bot { background: var(--ef-zsd-bot); color: var(--ef-zsd-top); }
.ef-zsd .ef-zsd__in { max-width: 56rem; margin: 0 auto; }
.ef-zsd .ef-zsd__h { margin: 0 0 1.5rem; padding: 0; opacity: 1; color: inherit; font-family: 'Parkinsans', 'Space Grotesk', sans-serif; font-size: clamp(2rem, 7cqw, 3rem); font-weight: 800; line-height: 1; letter-spacing: normal; text-transform: none; }
.ef-zsd .ef-zsd__p { margin: 0; padding: 0; opacity: 0.9; color: inherit; font-size: 1.25rem; line-height: 1.75rem; }
.ef-zsd .ef-zsd__bp { font-weight: 500; }
.ef-zsd .ef-zsd__edge { width: 100%; height: var(--ef-zsd-edge); background: var(--ef-zsd-bot); }
.ef-zsd .ef-zsd__svg { display: block; width: 100%; height: var(--ef-zsd-edge); }
.ef-zsd .ef-zsd__poly { fill: var(--ef-zsd-top); }

JavaScript

// Enrichr Library · https://enrichr.dev
// Zigzag section divider: two full-width bands joined by a wide, flat, uniform zigzag. The edge is one SVG polygon in a
// 1000 by 40 viewBox, stretched to the full width with preserveAspectRatio none, filled with the top band's colour and
// sitting on a strip of the bottom band's colour, so the teeth of the top band bite into the one below. Nothing moves, as
// in the original pen; reduced motion changes nothing. Changing the tooth count or edge height rebuilds the points.
// mount(root, { topTitle, topText, bottomTitle, bottomText, teeth (count), edge (height, px), top (top band colour),
//               bottom (bottom band colour), light (text on the top band) })
function mount(root, o) {
  var box = root.querySelector(".ef-zsd");
  var poly = root.querySelector(".ef-zsd__poly");
  var parts = { topTitle: ".ef-zsd__th", topText: ".ef-zsd__tp", bottomTitle: ".ef-zsd__bh", bottomText: ".ef-zsd__bp" };
  var opts = {};

  function num(v, d) { v = Number(v); return isFinite(v) ? v : d; }
  // n teeth = 2n straight runs across the 1000 unit width, alternating bottom (40) and top (0), then closed along the top
  function points(n) {
    var p = [], k, step = 1000 / (2 * n);
    for (k = 0; k <= 2 * n; k++) p.push((Math.round(k * step * 100) / 100) + "," + (k % 2 ? 0 : 40));
    return p.join(" ") + " 1000,0 0,0";
  }
  function update(partial) {
    opts = Object.assign({
      topTitle: "Build the top half", topText: "A full-width band, joined to the next one by a wide zigzag edge.",
      bottomTitle: "Then the bottom half", bottomText: "The second band picks up where the teeth leave off.",
      teeth: 5, edge: 40, top: "#005236", bottom: "#e6ff55", light: "#fbf8f1"
    }, opts, partial || {});
    opts.teeth = Math.max(1, Math.min(40, Math.round(num(opts.teeth, 5))));
    opts.edge = Math.max(4, Math.min(200, num(opts.edge, 40)));
    for (var k in parts) { var el = root.querySelector(parts[k]); if (el && el.textContent !== String(opts[k])) el.textContent = String(opts[k]); }
    poly.setAttribute("points", points(opts.teeth));
    box.style.setProperty("--ef-zsd-edge", opts.edge + "px");
    box.style.setProperty("--ef-zsd-top", opts.top);
    box.style.setProperty("--ef-zsd-bot", opts.bottom);
    box.style.setProperty("--ef-zsd-light", opts.light);
  }
  update(o);
  return { update: update, destroy: function () {} };
}

// Usage
var fx = mount(document.querySelector("#my-root"), {"topTitle":"Build the top half","topText":"A full-width band, joined to the next one by a wide zigzag edge.","bottomTitle":"Then the bottom half","bottomText":"The second band picks up where the teeth leave off.","teeth":5,"edge":40,"top":"#005236","bottom":"#e6ff55","light":"#fbf8f1"});

How it works

Between the two bands sits a strip in the bottom band's colour holding one SVG polygon in the top band's colour. The polygon's points zigzag between the bottom and top of a 1000 by 40 viewBox and then close along the top edge, and preserveAspectRatio none stretches it to the full width and the chosen height, so the teeth stay wide and flat at any size. Changing the tooth count rebuilds the points. Nothing animates, exactly like the original pen.

Accessibility and performance

The zigzag is decorative and hidden from assistive technology; the two bands hold real headings and paragraphs. Nothing moves, so reduced motion needs no change, and nothing needs keyboard input. Check the contrast of the text colours if you change the band colours.

One small static SVG and plain CSS. No script runs after the first render, apart from rebuilding the points when a setting changes.

SectionBand
TechniquesReveal and intro
StackVanilla JS, SVG
Works withReact, Next.js, Vanilla
DependenciesNone
Statustested
On CodePenOpen the live pen on our CodePen account; fork it to experiment.
Inspired byWide Uniform ZigZag Sections by Enrichr on CodePen. Original code, written from scratch.

Find this technique on a real site

Want to see this technique in the wild? Check any site with the free tools, then build your own version from this page.

Check which animation library a site uses · Extract a site's design tokens · Browse site breakdowns

Licence and provenance

Free to use in personal and client projects during the preview. Final licence terms will be published here before any paid tier starts; ask before reselling the code as a collection.

This is original code. The technique is a common one that we studied by measuring award-winning sites with Enrichr; no third-party code, shaders, fonts or images are used.

Circle reveal introA gate button opens a circle from the click point that grows to reveal the page. Mask shape sectionsThe same image revealed through a circle, a diamond and a band as each section scrolls into view. Clip reveal menuText rows that open a generated pattern behind the word with a clip-path transition on hover, while the word inverts along the edge.

FAQ

Can I flip it so the bottom band bites up?

Yes. In the CSS, give .ef-zsd__edge the top band colour and .ef-zsd__poly the bottom band colour, then add transform: rotate(180deg) to .ef-zsd__svg.

Why do the teeth get wider on big screens?

The polygon is stretched to the full width, so a fixed number of teeth spreads out as the screen widens. That is what keeps the original's wide, uniform look. Raise the tooth count for a finer edge.

Can I use it without JavaScript?

Yes. The markup already contains the default five-tooth polygon, so it renders correctly with the CSS alone; the script only applies your settings.