ExklusivBeta

Countdown <mv-countdown>

Countdown zu einem Datum für Launches, Sales, Webinare und Events: Tage, Stunden, Minuten und Sekunden (konfigurierbar) in tabellarischen Ziffern mit plural-korrekten Labels aus Intl, in den Varianten plain, card oder compact. Die Details, die andere falsch machen: Jeder Tick wird aus der Uhrzeit neu berechnet und auf die nächste echte Sekundengrenze getaktet (kein Driften, kein sich aufsummierendes Intervall), das Rendern pausiert, solange der Tab verborgen oder der Timer außerhalb des Bildschirms ist, und synchronisiert sich bei der Rückkehr neu, Hover oder Fokus zeigt das Ziel in der Ortszeit des Besuchers („Wed, Oct 7, 9:01 AM your time“), und er zeigt nie negative Zahlen: Bei null wechselt er zu deinem per Slot eingefügten „ended“-Inhalt und feuert mv-end genau einmal, selbst wenn die Seite erst nach dem Ende geöffnet wird, und Screenreader erhalten ein ruhiges role=timer, dessen Name einmal pro Minute statt jede Sekunde aktualisiert wird.

KategorieDatenanzeige
TypWeb Component (<mv-countdown>)
StatusBeta
Keywordsexclusive, light, countdown, timer, launch, sale, event, webinar, time-zone, intl

When to use

  • A launch, product drop or event page needs a live countdown to a fixed date and time
  • A sale or promo banner should show a compact time left that turns into an ended message on its own
  • A webinar or live stream page should swap the timer for a Join button when it starts
  • Visitors in other time zones need to see when the moment falls in their own local time

Avoid when

  • The moment should read as relative text like “in 2 days” or “3 minutes ago” rather than a ticking timer → use Relative Time instead
  • A deadline must be read as a date across two time zones with the day shift spelled out → use Dateline instead
  • A prompt waits a few seconds and then takes a default choice if nobody answers → use Timed Choice instead

Installation

node scripts/add.mjs countdown --out ./src/marvelous

KI-Agent mit dem MCP-Server von Marvelous UI: install_components({ slugs: ["countdown"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Kopierte Dateien (inklusive Abhängigkeiten): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/observe.js, components/countdown/countdown.js, components/countdown/countdown.css.

Verwendung

Schnellstart mit dem kleinsten funktionierenden Markup:

<mv-countdown to="2026-10-07T09:01:00+02:00"></mv-countdown>

Referenz-Markup zum Starten und Anpassen mit Attributen, data-* und CSS-Variablen:

<div id="mv-countdown-demo" style="display:grid;gap:1rem;width:100%;max-width:560px">
  <div style="display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:.4rem .75rem;padding:.6rem 1rem;border-radius:var(--mv-radius-lg);background:var(--mv-accent-subtle);color:var(--mv-fg);font-size:.875rem">
    <strong>Summer sale: 30% off annual plans</strong>
    <span style="color:var(--mv-fg-muted)">Ends in <mv-countdown data-variant="compact" data-offset="187200" to="2026-10-01T18:00:00-04:00" label="Summer sale">Oct 1, 6:00 PM EDT<template slot="ended">The sale has ended</template></mv-countdown></span>
  </div>

  <section style="display:grid;justify-items:center;gap:1rem;padding:1.75rem 1rem 1.5rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:var(--mv-surface);box-shadow:var(--mv-shadow-md);text-align:center">
    <div style="display:grid;gap:.3rem">
      <span style="font-size:.75rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--mv-accent-fg)">Lumen 2.0</span>
      <strong style="font-size:1.25rem">Doors open in</strong>
    </div>
    <mv-countdown data-variant="card" data-offset="1123265" to="2026-10-07T09:01:00+02:00" label="Launch">Wednesday, October 7, 9:01 AM CEST</mv-countdown>
    <span style="font-size:.8rem;color:var(--mv-fg-subtle)">Hover or focus the timer to see the time in your time zone.</span>
  </section>

  <div style="display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:.75rem;padding:.9rem 1rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-lg);background:var(--mv-bg-subtle)">
    <div style="display:grid;gap:.15rem">
      <strong style="font-size:.9rem">Webinar: Designing for 12 languages</strong>
      <span style="font-size:.8rem;color:var(--mv-fg-muted)">with Aiko Tanaka and Mateo Ruiz</span>
    </div>
    <mv-countdown id="mv-countdown-demo-live" units="m s" data-offset="9" to="2026-10-01T16:00:00Z" label="Webinar" style="--mv-countdown-size:1.5em">
      <template slot="ended">
        <span style="width:.5rem;height:.5rem;border-radius:50%;background:var(--mv-danger);box-shadow:0 0 0 3px color-mix(in oklch,var(--mv-danger) 25%,transparent)"></span>
        <span style="font-size:.8rem;font-weight:600">Live now</span>
        <a class="mv-button" data-size="sm" href="#">Join webinar</a>
      </template>
    </mv-countdown>
  </div>

  <div style="display:flex;justify-content:center">
    <button class="mv-button" data-variant="ghost" data-size="sm" id="mv-countdown-demo-replay">Replay webinar start</button>
  </div>
</div>

<script type="module">
  // Demo only: aim each timer a fixed number of seconds ahead so it always runs.
  const root = document.getElementById("mv-countdown-demo");
  const aim = (el) => { el.to = new Date(Date.now() + Number(el.dataset.offset) * 1000).toISOString(); };
  root.querySelectorAll("mv-countdown[data-offset]").forEach(aim);
  const live = document.getElementById("mv-countdown-demo-live");
  document.getElementById("mv-countdown-demo-replay").addEventListener("click", () => aim(live));
</script>

API

Attributes

NameTypDefaultDescription
toISO 8601 date-timeTarget moment, ideally with an offset or Z (2026-10-07T09:01:00+02:00) so every visitor counts down to the same instant. Missing or invalid: dashes and data-state="idle".
unitsstringd h m sUnits to show, largest first, separated by spaces or commas (d, h, m, s, or days, hours…). The largest shown unit absorbs the rest (units="h m" → 49 hours 05 minutes); without seconds the smallest unit rounds up so zero only appears at the end. Leading zero units drop out while more than two remain.
labelstringWhat is being counted down to, prefixed to the accessible name (“Launch: 12 days, 4 hours, 5 minutes remaining”).
localeBCP 47 tagnearest lang, else en-USLocale for digits, unit labels, the local-time tip and the accessible name.
data-variantcard | compact(plain)Plain: large digits with labels and colons. Card: one tile per unit. Compact: one line of text (2d 04h 05m 06s) for bars, buttons and sentences.
data-stateidle | running | endedSet by the component (for styling).
data-swappedbooleanSet by the component while the ended content replaces the digits.

Properties

NameTypDescription
to / units / label / localestringMirror the attributes.
stringsobjectOverride default texts: remaining (“{time} remaining”), soon (“Less than a minute remaining”), ended (“Ended”), labelled (“{label}: {status}”), local (“{date} your time”).
remainingnumberMilliseconds left, 0 once ended, NaN without a valid target (read-only).
endedbooleanTrue once the target has passed (read-only).

Events

NameDescription
mv-endFired once per target when it reaches zero, or one task after the upgrade when the page opens after the end; detail = { to: Date, late: boolean }. Cancelable: preventDefault() keeps the zeroed timer instead of swapping in the ended content.

Content structure

NameDescription
(text)Fallback shown before JavaScript runs (SSR, no JS), e.g. the date written out; replaced by the live display on upgrade.
ended<template slot="ended"> or any [slot=ended] child: shown instead of the digits at zero (a message, a Join or Shop button).

CSS classes

NameDescription
mv-countdown-units / -unit / -value / -labelGenerated digits (aria-hidden; data-unit = d | h | m | s).
mv-countdown-tipLocal-time tooltip (role=tooltip, linked with aria-describedby).
mv-countdown-endedAdded to the ended content.

CSS variables

NameDefaultDescription
--mv-countdown-size2.25em / 2em card / 1em compactDigit size.
--mv-countdown-gap0.8 × size / var(--mv-space-2) card / 0.4em compactSpace between units.
--mv-countdown-colorvar(--mv-fg)Digit color.
--mv-countdown-separatorvar(--mv-fg-subtle)Colon color (plain variant).

Accessibility

The host is a role=timer with aria-live=off, so nothing is announced by itself; the visible digits are aria-hidden and the accessible name is a full sentence built with Intl.NumberFormat units and Intl.ListFormat, rewritten at most once a minute (“Launch: 2 days, 4 hours, 5 minutes remaining”, then “Less than a minute remaining”, then “Ended”). The timer is focusable (tabindex=0 unless you set one) and describes itself with the local-time tooltip (role=tooltip, aria-describedby), shown on hover, keyboard focus or tap focus, hoverable and dismissed with Escape (WCAG 1.4.13). When ended content is swapped in, the timer role, name and tab stop are removed so your message or button reads as normal content. All texts come from strings and Intl in the chosen locale (plural-correct units, local digits). Tabular digits keep the width steady; no continuous motion, the ended content fades in (instant under reduced motion or data-motion=reduce). Forced colors: tooltip and tiles get system borders, focus shows a system outline. SSR-safe (no window access at import); the fallback text is readable without JavaScript.

Diese Seite wurde mit KI übersetzt. Übersetzungsfehler melden