12 CSS Infinite Marquee Designs06 / 12

Pure CSSMIT licensed

Flash Sale Announcement Bar — Fast, High-Contrast, Dismissible

The urgency pattern e-commerce lives on: a fast, high-contrast marquee bar pinned to the top of the page shouting a limited-time offer, with a working promo-code link, a dismiss button that collapses the bar with pure CSS (:has() + a checkbox), and a grid-template-rows animation so the page content slides up smoothly instead of jumping.

Published

Live Demo
Try it

The code

<section class="mqs-06" aria-label="Flash sale announcement bar demo">
  <input class="mqs-06__closeBox" type="checkbox" id="mqs-06-close">
  <div class="mqs-06__collapse">
    <div class="mqs-06__clip">
      <div class="mqs-06__bar">
        <div class="mqs-06__viewport">
          <div class="mqs-06__group">
            <span class="mqs-06__msg">Flash sale — 40% off everything ends tonight</span><span class="mqs-06__zap" aria-hidden="true">⚡</span>
            <span class="mqs-06__msg">Use code <a class="mqs-06__code" href="#deal">BLITZ40</a> at checkout</span><span class="mqs-06__zap" aria-hidden="true">⚡</span>
          </div>
          <div class="mqs-06__group" aria-hidden="true">
            <span class="mqs-06__msg">Flash sale — 40% off everything ends tonight</span><span class="mqs-06__zap">⚡</span>
            <span class="mqs-06__msg">Use code <span class="mqs-06__code">BLITZ40</span> at checkout</span><span class="mqs-06__zap">⚡</span>
          </div>
        </div>
        <label class="mqs-06__closeBtn" for="mqs-06-close" title="Dismiss announcement"><span aria-hidden="true">✕</span><span class="mqs-06__sr">Dismiss announcement</span></label>
      </div>
    </div>
  </div>
  <div class="mqs-06__page">
    <p class="mqs-06__hint">↑ Page content — dismiss the bar and watch it glide up (pure CSS, no JS).</p>
  </div>
</section>
.mqs-06,
.mqs-06 *,
.mqs-06 *::before,
.mqs-06 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.mqs-06 {
  min-height: 100vh;
  width: 100%;
  --gap: 36px;
  --speed: 14s;
  font-family: 'Segoe UI',system-ui,sans-serif;
  background: #fafaf9;
}

.mqs-06__closeBox {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.mqs-06__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.mqs-06__collapse {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows .3s ease;
}

.mqs-06:has(.mqs-06__closeBox:checked) .mqs-06__collapse {
  grid-template-rows: 0fr;
}

.mqs-06__clip {
  min-height: 0;
  overflow: hidden;
}

.mqs-06__bar {
  display: flex;
  align-items: stretch;
  background: #fde047;
  border-bottom: 2px solid #1c1917;
}

.mqs-06__viewport {
  flex: 1;
  display: flex;
  gap: var(--gap);
  overflow: hidden;
  padding-block: 11px;
}

.mqs-06__group {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--gap);
  animation: mqs-06-scroll var(--speed) linear infinite;
  will-change: transform;
}

@keyframes mqs-06-scroll {
  to {
    transform: translateX(calc(-100% - var(--gap)));
  }
}

.mqs-06__viewport:hover .mqs-06__group,
.mqs-06__viewport:focus-within .mqs-06__group {
  animation-play-state: paused;
}

.mqs-06__msg {
  flex: none;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: #1c1917;
}

.mqs-06__zap {
  flex: none;
  font-size: 12px;
}

.mqs-06__code {
  color: #1c1917;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 2px;
  border-radius: 3px;
}

.mqs-06__code:hover {
  background: #1c1917;
  color: #fde047;
  text-decoration: none;
  padding-inline: 4px;
}

.mqs-06__code:focus-visible {
  outline: 2px solid #1c1917;
  outline-offset: 2px;
}

.mqs-06__closeBtn {
  flex: none;
  display: grid;
  place-items: center;
  width: 42px;
  font-size: 14px;
  font-weight: 700;
  color: #1c1917;
  cursor: pointer;
  border-left: 2px solid #1c1917;
  transition: background .15s;
}

.mqs-06__closeBtn:hover {
  background: #facc15;
}

.mqs-06__closeBox:focus-visible ~ .mqs-06__collapse .mqs-06__closeBtn {
  outline: 2px solid #1c1917;
  outline-offset: -4px;
}

.mqs-06__page {
  padding: 56px 24px 64px;
  text-align: center;
}

.mqs-06__hint {
  font-size: 13px;
  color: #a8a29e;
}

@media (prefers-reduced-motion: reduce) {
  .mqs-06__group {
    animation: none;
  }

  .mqs-06__viewport {
    overflow-x: auto;
  }

  .mqs-06__collapse {
    transition: none;
  }
}
/* No JavaScript — dismissal is a checkbox + :has(:checked) flipping grid-template-rows 1fr→0fr (the modern animate-to-height-auto trick). Add 2 lines of localStorage if dismissal should persist. */
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 Infinite Marquee Design 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: Flash Sale Announcement Bar — Fast, High-Contrast, Dismissible
Source: https://codefronts.com/motion/css-infinite-marquee/flash-sale-announcement-bar/

The urgency pattern e-commerce lives on: a fast, high-contrast marquee bar pinned to the top of the page shouting a limited-time offer, with a working promo-code link, a dismiss button that collapses the bar with pure CSS (:has() + a checkbox), and a grid-template-rows animation so the page content slides up smoothly instead of jumping.
## HTML
```html
<section class="mqs-06" aria-label="Flash sale announcement bar demo">
  <input class="mqs-06__closeBox" type="checkbox" id="mqs-06-close">
  <div class="mqs-06__collapse">
    <div class="mqs-06__clip">
      <div class="mqs-06__bar">
        <div class="mqs-06__viewport">
          <div class="mqs-06__group">
            <span class="mqs-06__msg">Flash sale — 40% off everything ends tonight</span><span class="mqs-06__zap" aria-hidden="true">⚡</span>
            <span class="mqs-06__msg">Use code <a class="mqs-06__code" href="#deal">BLITZ40</a> at checkout</span><span class="mqs-06__zap" aria-hidden="true">⚡</span>
          </div>
          <div class="mqs-06__group" aria-hidden="true">
            <span class="mqs-06__msg">Flash sale — 40% off everything ends tonight</span><span class="mqs-06__zap">⚡</span>
            <span class="mqs-06__msg">Use code <span class="mqs-06__code">BLITZ40</span> at checkout</span><span class="mqs-06__zap">⚡</span>
          </div>
        </div>
        <label class="mqs-06__closeBtn" for="mqs-06-close" title="Dismiss announcement"><span aria-hidden="true">✕</span><span class="mqs-06__sr">Dismiss announcement</span></label>
      </div>
    </div>
  </div>
  <div class="mqs-06__page">
    <p class="mqs-06__hint">↑ Page content — dismiss the bar and watch it glide up (pure CSS, no JS).</p>
  </div>
</section>
```
## CSS
```css
.mqs-06,
.mqs-06 *,
.mqs-06 *::before,
.mqs-06 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.mqs-06 {
  min-height: 100vh;
  width: 100%;
  --gap: 36px;
  --speed: 14s;
  font-family: 'Segoe UI',system-ui,sans-serif;
  background: #fafaf9;
}

.mqs-06__closeBox {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.mqs-06__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.mqs-06__collapse {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows .3s ease;
}

.mqs-06:has(.mqs-06__closeBox:checked) .mqs-06__collapse {
  grid-template-rows: 0fr;
}

.mqs-06__clip {
  min-height: 0;
  overflow: hidden;
}

.mqs-06__bar {
  display: flex;
  align-items: stretch;
  background: #fde047;
  border-bottom: 2px solid #1c1917;
}

.mqs-06__viewport {
  flex: 1;
  display: flex;
  gap: var(--gap);
  overflow: hidden;
  padding-block: 11px;
}

.mqs-06__group {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--gap);
  animation: mqs-06-scroll var(--speed) linear infinite;
  will-change: transform;
}

@keyframes mqs-06-scroll {
  to {
    transform: translateX(calc(-100% - var(--gap)));
  }
}

.mqs-06__viewport:hover .mqs-06__group,
.mqs-06__viewport:focus-within .mqs-06__group {
  animation-play-state: paused;
}

.mqs-06__msg {
  flex: none;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: #1c1917;
}

.mqs-06__zap {
  flex: none;
  font-size: 12px;
}

.mqs-06__code {
  color: #1c1917;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 2px;
  border-radius: 3px;
}

.mqs-06__code:hover {
  background: #1c1917;
  color: #fde047;
  text-decoration: none;
  padding-inline: 4px;
}

.mqs-06__code:focus-visible {
  outline: 2px solid #1c1917;
  outline-offset: 2px;
}

.mqs-06__closeBtn {
  flex: none;
  display: grid;
  place-items: center;
  width: 42px;
  font-size: 14px;
  font-weight: 700;
  color: #1c1917;
  cursor: pointer;
  border-left: 2px solid #1c1917;
  transition: background .15s;
}

.mqs-06__closeBtn:hover {
  background: #facc15;
}

.mqs-06__closeBox:focus-visible ~ .mqs-06__collapse .mqs-06__closeBtn {
  outline: 2px solid #1c1917;
  outline-offset: -4px;
}

.mqs-06__page {
  padding: 56px 24px 64px;
  text-align: center;
}

.mqs-06__hint {
  font-size: 13px;
  color: #a8a29e;
}

@media (prefers-reduced-motion: reduce) {
  .mqs-06__group {
    animation: none;
  }

  .mqs-06__viewport {
    overflow-x: auto;
  }

  .mqs-06__collapse {
    transition: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — dismissal is a checkbox + :has(:checked) flipping grid-template-rows 1fr→0fr (the modern animate-to-height-auto trick). Add 2 lines of localStorage if dismissal should persist. */
```

How this works

The bar runs the duplicated-group loop at an urgent 14s with bold uppercase copy and a lightning glyph between repeats. Inside the moving track sits a real <a> for the promo code — and this is where :focus-within earns its keep: the instant the link receives keyboard focus, the whole bar pauses, so tab users can actually activate the moving link. Without that rule a focusable element inside a marquee is a WCAG failure waiting to happen.

Dismissal is pure CSS: a visually-hidden checkbox before the bar, a ✕ styled as its label, and .mqs-06:has(#mqs-06-close:checked) .mqs-06__collapse { grid-template-rows: 0fr }. The bar's content sits in a one-row grid whose row animates 1fr → 0fr — the modern height-auto animation trick — so the announcement squeezes shut in 300ms and the page below glides up with no layout snap.

High contrast is doing the marketing here: near-black text on a saturated yellow band hits ~13:1 contrast, so even at 14s the copy stays legible in peripheral vision — which is exactly how announcement bars are consumed.

Techniques used: fast-loop duplicated groups with focusable link + :focus-within pause · pure-CSS dismiss via :has(:checked) · animated height-auto collapse with grid-template-rows 1fr→0fr · interpolate-size-free technique (works today everywhere grid does) · 13:1 contrast band for peripheral legibility

Make it yours

  • Urgency dial: --speed between 10s (aggressive) and 18s (calm); below 10s the copy becomes decoration, not information.
  • Persist dismissal: this demo resets on reload by design; in production add 2 lines of JS to store the checkbox state in localStorage.
  • Collapse duration: the .3s on grid-template-rows; pair it with an opacity fade for extra polish.
  • Palette: yellow/black is the highest-recall combo; red bands should lighten the text track to keep ≥4.5:1.

Gotchas — read before shipping

  • A link inside a marquee REQUIRES the :focus-within pause rule — moving focus targets are both a usability bug and a WCAG 2.4.7 problem.
  • Animate grid-template-rows, not height — height:auto→0 doesn't transition (without interpolate-size), the grid row trick works everywhere today.
  • min-height:0 (or overflow:hidden) on the grid child is mandatory or the row refuses to shrink below content size.
  • Don't stack two moving bars (announcement + press logos) in one viewport-height — motion competes and both get ignored.

Browser support

ChromeSafariFirefoxEdge
105+15.4+121+105+

:has() powers the dismiss; without it the ✕ simply doesn't collapse the bar (content unaffected). grid-template-rows transitions are baseline since 2023.

Techniques used in this demo

Search CodeFronts

Loading…