ExklusivBeta

As Of <mv-as-of>

Zeitpunkt-Steuerung („as of“) für Daten-Apps, die die Vergangenheit zeigen können, damit historische Zahlen nie für aktuelle gehalten werden. Drei Zeilen bleiben immer sichtbar: Viewing (der gewählte Moment oder Live-Daten, mit einem Datums- oder Datum-Uhrzeit-Feld, Schritten von ±1 Stunde/Tag/Woche/Monat/Quartal/Jahr, die am Monatsende haften bleiben, und einem Menü Jump to mit Voreinstellungen wie „End of last quarter“ sowie zuletzt besuchten Zielen), Now (eine Live-Uhr mit Zeitzone) und Left from (der Moment, an dem du vor dem letzten Sprung warst, mit einem Klick zurück, sodass der Vergleich zweier Daten zum Umschalten wird). Solange du die Vergangenheit ansiehst, sagen ein ruhiges Banner („You’re viewing data as of Jun 30, 2026. Return to now“) und optionale Rahmen um deine Datenbereiche das in Worten, und jedes [data-as-of-live-only]-Steuerelement im Bereich (Buttons zum Bearbeiten, Anpassen, Speichern) wird inert, bis du zurückkehrst. Jeder Sprung läuft über ein abbrechbares mv-travel, dessen waitUntil(promise) einen Wartezustand zeigt, während du neu lädst, und bei fehlgeschlagenem Laden mit einer Ansage zurückrollt; schnelle Schritte fassen sich zu einem einzigen Sprung zusammen, die Zukunft wird auf live begrenzt und alles vor min auf die frühesten verfügbaren Daten, der Moment kann in einem URL-Parameter stehen (mit Unterstützung für Zurück/Vor), und die letzten Ziele können in localStorage gespeichert werden.

KategorieNavigation
TypWeb Component (<mv-as-of>)
StatusBeta
KitNavigation für Power-User
Installiert auchbutton, input, select
Keywordsexclusive, culture, point-in-time, as-of, time-travel, history, snapshot, audit, versioning, dashboard, report, financial, period-end, read-only, url-state, temporal

When to use

  • A dashboard or report can be recomputed as of a past date and viewers must never confuse it with current figures
  • An audit or history view shows a configuration, permissions or inventory exactly as it stood at a given moment
  • Financial statements are reviewed at period ends (month, quarter, year) with quick back-and-forth between two dates
  • Editing must be blocked while someone is looking at a historical snapshot, and resume as soon as they return to now

Avoid when

  • The user picks a date as form input (due date, booking, birthday), not a moment to view the whole app at → use Date Picker instead
  • The job is to display one moment or deadline clearly across time zones → use Dateline instead
  • The need is to undo or compare edits of a single form or document rather than view data at a past moment → use Rewind instead

Installation

node scripts/add.mjs as-of --out ./src/marvelous

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

Kopierte Dateien (inklusive Abhängigkeiten): tokens/tokens.css, core/base.css, core/dismiss.js, core/dom.js, core/element.js, core/focus.js, core/observe.js, core/position.js, components/as-of/as-of.js, components/as-of/as-of.css, components/button/button.css, components/input/input.css, components/select/select.css.

Verwendung

Schnellstart mit dem kleinsten funktionierenden Markup:

<mv-as-of frame="#report" min="2024-01-01"></mv-as-of>
<section id="report">…your figures…</section>

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

<div id="ao-demo" style="width:min(100%,66rem);margin-inline:auto">
  <style>
    #ao-demo { display:grid; grid-template-columns:minmax(0,1fr); gap:2.5rem; align-content:start }
    #ao-demo .ao-main { display:grid; grid-template-columns:minmax(0,1fr) 24rem; gap:1.5rem; align-items:start }
    #ao-demo .ao-report { display:grid; gap:1rem; padding:1.25rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface); box-shadow:var(--mv-shadow-xs) }
    #ao-demo .ao-top { display:flex; align-items:flex-start; justify-content:space-between; gap:.75rem 1rem; flex-wrap:wrap }
    #ao-demo .ao-top h3 { margin:0; font-size:1.0625rem; letter-spacing:-.01em }
    #ao-demo .ao-top p { margin:.125rem 0 0; color:var(--mv-fg-muted); font-size:.8125rem }
    #ao-demo .ao-actions { display:flex; gap:.5rem; flex-wrap:wrap }
    #ao-demo .ao-kpis { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:.75rem; margin:0 }
    #ao-demo .ao-kpi { display:grid; gap:.25rem; padding:.75rem .875rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-lg); background:var(--mv-bg-subtle) }
    #ao-demo .ao-kpi dt { color:var(--mv-fg-muted); font-size:.75rem }
    #ao-demo .ao-kpi dd { margin:0; font-size:1.25rem; font-weight:650; letter-spacing:-.02em; font-variant-numeric:tabular-nums }
    #ao-demo .ao-kpi small { display:block; color:var(--mv-fg-muted); font-size:.6875rem; font-weight:500; letter-spacing:0 }
    #ao-demo table { width:100%; border-collapse:collapse; font-size:.8125rem }
    #ao-demo caption { text-align:start; padding:0 0 .5rem; color:var(--mv-fg-muted); font-size:.75rem; font-weight:500 }
    #ao-demo th, #ao-demo td { padding:.5rem .625rem; border-bottom:1px solid var(--mv-border); text-align:start; white-space:nowrap }
    #ao-demo th { color:var(--mv-fg-muted); font-size:.6875rem; font-weight:600; letter-spacing:.04em; text-transform:uppercase }
    #ao-demo td.num, #ao-demo th.num { text-align:end; font-variant-numeric:tabular-nums }
    #ao-demo tr:last-child td { border-bottom:0 }
    #ao-demo .ao-side { display:grid; gap:.875rem }
    #ao-demo .ao-panel { display:grid; gap:.625rem; padding:.875rem 1rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-lg); background:var(--mv-bg-subtle); font-size:.8125rem }
    #ao-demo .ao-log { min-height:1.25rem; margin:0; color:var(--mv-fg-subtle); font:.75rem/1.45 var(--mv-font-mono); overflow-wrap:anywhere }
    #ao-demo .ao-h { margin:0 0 .75rem; font-size:.8125rem; font-weight:600; color:var(--mv-fg-muted) }
    #ao-demo .ao-flags { padding:1rem 1.25rem .75rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface); margin-top:1.25rem }
    #ao-demo .ao-flag-name { font-family:var(--mv-font-mono); font-size:.75rem }
    @media (max-width:62rem) { #ao-demo .ao-main { grid-template-columns:minmax(0,1fr) } }
    @media (max-width:40rem) { #ao-demo .ao-kpis { grid-template-columns:repeat(2,minmax(0,1fr)) } #ao-demo .ao-hide-sm { display:none } #ao-demo td:first-child { white-space:normal } #ao-demo .ao-report, #ao-demo .ao-flags { padding-inline:.875rem } #ao-demo th, #ao-demo td { padding-inline:.375rem } }
  </style>

  <!-- 1. Inventory report with the stacked control beside it -->
  <div class="ao-main" id="ao-app">
    <section class="ao-report" id="ao-report" aria-labelledby="ao-report-title">
      <div class="ao-top">
        <div>
          <h3 id="ao-report-title">Inventory &amp; revenue</h3>
          <p>Northwind Supply · all warehouses</p>
        </div>
        <div class="ao-actions">
          <button type="button" class="mv-button" data-variant="outline" data-size="sm" data-as-of-live-only>Adjust stock</button>
          <button type="button" class="mv-button" data-size="sm" data-as-of-live-only>Edit forecast</button>
        </div>
      </div>
      <dl class="ao-kpis" id="ao-kpis"></dl>
      <table>
        <caption id="ao-caption">Top SKUs by value on hand</caption>
        <thead><tr><th scope="col">SKU</th><th scope="col" class="ao-hide-sm">Warehouse</th><th scope="col" class="num">On hand</th><th scope="col" class="num">Value</th><th scope="col">Status</th></tr></thead>
        <tbody id="ao-rows"></tbody>
      </table>
    </section>

    <div class="ao-side">
      <mv-as-of id="ao-main" param="asof" frame="#ao-report" scope="#ao-app" min="2024-01-01" storage-key="ao-demo-recent"></mv-as-of>
      <div class="ao-panel">
        <label class="mv-choice" data-control="end">
          <input type="checkbox" role="switch" class="mv-switch" id="ao-fail">
          <span class="mv-choice-text"><span class="mv-choice-title">Make the next fetch fail</span><span class="mv-choice-description">The jump is rolled back and announced</span></span>
        </label>
        <p class="ao-log" id="ao-log" aria-live="polite">Loading…</p>
      </div>
    </div>
  </div>

  <!-- 2. Bar layout, minute precision: a production config audited at a past moment -->
  <div>
    <p class="ao-h">Feature flags · production (bar layout, minute precision)</p>
    <div id="ao-flags-app">
      <mv-as-of id="ao-bar" layout="bar" precision="minute" steps="hour, day, week" unit="hour" presets="7d, 30d, last-month" frame="#ao-flags" scope="#ao-flags-app" label="Config point in time"></mv-as-of>
      <div class="ao-flags" id="ao-flags">
        <table>
          <caption id="ao-flag-caption">Flags</caption>
          <thead><tr><th scope="col">Flag</th><th scope="col">State</th><th scope="col" class="num">Rollout</th><th scope="col" class="ao-hide-sm">Changed by</th><th scope="col"><span class="mv-sr-only">Actions</span></th></tr></thead>
          <tbody id="ao-flag-rows"></tbody>
        </table>
      </div>
    </div>
  </div>

  <script type="module">
    const main = document.getElementById("ao-main");
    const bar = document.getElementById("ao-bar");
    const log = document.getElementById("ao-log");
    const fail = document.getElementById("ao-fail");
    const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 });
    const int = new Intl.NumberFormat("en-US");
    const short = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric" });

    // Deterministic "database": the same moment always returns the same figures.
    const seed = (str) => { let h = 2166136261; for (const c of str) { h ^= c.charCodeAt(0); h = Math.imul(h, 16777619); } return () => ((h = Math.imul(h ^ (h >>> 15), 2246822507) ^ Math.imul(h ^ (h >>> 13), 3266489909)) >>> 0) / 4294967296; };
    const cell = (text, cls) => { const td = document.createElement("td"); if (cls) td.className = cls; td.append(text); return td; };
    const badge = (text, variant) => { const b = document.createElement("span"); b.className = "mv-badge"; b.dataset.variant = variant; b.dataset.size = "sm"; b.textContent = text; return b; };

    const SKUS = [
      ["Ceramic pour-over set", "Rotterdam"], ["Linen apron, charcoal", "Reno"], ["Cast-iron skillet 26 cm", "Rotterdam"],
      ["Oak cutting board", "Monterrey"], ["Stoneware mug, set of 4", "Reno"],
    ];
    function renderReport(date) {
      const key = date ? date.toISOString().slice(0, 10) : "live";
      const r = seed(key);
      const f = date ? 0.82 + r() * 0.16 : 1;
      const kpis = [
        ["Revenue, trailing 30 days", usd.format(Math.round(412_800 * f)), date ? `as of ${short.format(date)}` : "live"],
        ["Units on hand", int.format(Math.round(18_420 * (0.9 + r() * 0.2))), "5 warehouses"],
        ["Open orders", int.format(Math.round(236 * (0.7 + r() * 0.5))), "awaiting shipment"],
        ["Gross margin", `${(41.2 + (r() - 0.5) * 6).toFixed(1)}%`, "trailing 30 days"],
      ];
      document.getElementById("ao-kpis").replaceChildren(...kpis.map(([k, v, s]) => {
        const div = document.createElement("div"); div.className = "ao-kpi";
        const dt = document.createElement("dt"); dt.textContent = k;
        const dd = document.createElement("dd"); dd.textContent = v;
        const sm = document.createElement("small"); sm.textContent = s; dd.append(sm);
        div.append(dt, dd); return div;
      }));
      document.getElementById("ao-caption").textContent = date ? `Top SKUs by value on hand, as of ${short.format(date)}` : "Top SKUs by value on hand, live";
      document.getElementById("ao-rows").replaceChildren(...SKUS.map(([name, wh], i) => {
        const qty = Math.round((1400 - i * 210) * (0.25 + r() * 0.9));
        const value = qty * (18 + (5 - i) * 7);
        const status = qty < 320 ? ["Reorder", "danger"] : qty < 620 ? ["Low", "warning"] : ["In stock", "success"];
        const tr = document.createElement("tr");
        tr.append(cell(name), cell(wh, "ao-hide-sm"), cell(int.format(qty), "num"), cell(usd.format(value), "num"), cell(badge(...status)));
        return tr;
      }));
    }

    const FLAGS = ["checkout.one-page", "search.semantic-ranking", "billing.annual-discount", "mobile.dark-launch"];
    const PEOPLE = ["Aiko Tanaka", "Lucas Moreau", "Priya Raman", "Mateo Silva"];
    function renderFlags(date) {
      const key = date ? `${date.toISOString().slice(0, 13)}` : "live";
      const r = seed(key);
      document.getElementById("ao-flag-caption").textContent = date ? `Flags as of ${date.toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short" })}` : "Flags, live";
      document.getElementById("ao-flag-rows").replaceChildren(...FLAGS.map((flag, i) => {
        const on = (i + Math.floor(r() * 3)) % 3 !== 0;
        const rollout = on ? [5, 25, 50, 100][Math.floor(r() * 4)] : 0;
        const tr = document.createElement("tr");
        const name = document.createElement("span"); name.className = "ao-flag-name"; name.textContent = flag;
        const edit = document.createElement("button");
        edit.type = "button"; edit.className = "mv-button"; edit.dataset.variant = "ghost"; edit.dataset.size = "sm";
        edit.dataset.asOfLiveOnly = ""; edit.textContent = "Edit"; edit.setAttribute("aria-label", `Edit ${flag}`);
        tr.append(cell(name), cell(badge(on ? "On" : "Off", on ? "success" : "secondary")), cell(`${rollout}%`, "num"), cell(PEOPLE[(i + Math.floor(r() * 4)) % 4], "ao-hide-sm"), cell(edit, "num"));
        return tr;
      }));
    }

    // A fake API: 450 ms round trip, optionally failing once.
    const fetchAsOf = (reason) => new Promise((resolve, reject) => {
      if (reason === "api") return resolve();
      setTimeout(() => {
        if (fail.checked) { fail.checked = false; reject(new Error("504 Gateway Timeout")); } else resolve();
      }, 450);
    });
    const label = (d) => (d ? short.format(d) : "live");

    main.addEventListener("mv-travel", (e) => {
      const { from, to, reason } = e.detail;
      log.textContent = `GET /reports/inventory?asof=${e.detail.toValue ?? "now"} · ${reason}`;
      e.detail.waitUntil(fetchAsOf(reason).then(() => renderReport(to)));
    });
    main.addEventListener("mv-arrive", (e) => { log.textContent = `mv-arrive · ${label(e.detail.from)} → ${label(e.detail.to)}`; });
    main.addEventListener("mv-travel-error", (e) => { log.textContent = `Fetch failed (${e.detail.error.message}) · rolled back to ${label(e.detail.from)}`; renderReport(e.detail.from); });

    bar.addEventListener("mv-travel", (e) => { e.detail.waitUntil(fetchAsOf(e.detail.reason).then(() => renderFlags(e.detail.to))); });

    // Start in the past so all three rows are filled: live → end of last month → end of last quarter.
    const now = new Date();
    const lastQuarter = new Date(now.getFullYear(), Math.floor(now.getMonth() / 3) * 3, 0);
    let first = new Date(now.getFullYear(), now.getMonth(), 0);
    if (+first === +lastQuarter) first = new Date(now.getFullYear(), now.getMonth() - 1, 0);
    renderReport(main.date);
    renderFlags(null);
    if (main.live) {
      main.travel(first).then(() => main.travel(lastQuarter));
    }
    const twoDaysAgo = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 2, 16, 45);
    bar.travel(twoDaysAgo);
  </script>
</div>

Kulturelle Referenz

Zurück in die Zukunft, Robert Zemeckis (1985, Film). Das Armaturenbrett der Zeitmaschine zeigt immer drei Anzeigen, Zielzeit, Gegenwart und letzte Abfahrtszeit, sodass der Reisende nie den Überblick verliert, wann er sich im Verhältnis zu seinem Ausgangspunkt befindet. In der UI zeigt eine Daten-App dieselben drei Zeilen (den angezeigten Moment, die Live-Gegenwart und den vor dem letzten Sprung verlassenen Moment, einen Klick entfernt), mit einem ruhigen Banner, gerahmten Daten und gesperrten Bearbeitungen, solange du in der Vergangenheit bist.

API

Attributes

NameTypDefaultDescription
valuestring ("2026-06-30" | "2026-06-30T17:30" | ISO with offset)The viewed moment as a local wall time; absent = live. Reflected after every travel. Setting it from outside applies silently (no mv-travel, Left from unchanged): call travel() to go through the event flow. Future moments resolve to live, earlier ones than min are clamped to min.
precisionday | minutedayday: a date field, and a value means the end of that day (close of business); picking today means live. minute: a date-time field and values to the minute; anything within the last minute means live.
mindate or date-timeEarliest moment with data (retention start, company founding). Stepping and typing clamp to it and the clamp is announced; the back step is disabled there.
stepscomma list of hour | day | week | month | quarter | yearday, week, monthUnits offered in the step-size select (hidden when only one). Month, quarter and year steps keep end of month sticky: Mar 31 → Apr 30 → May 31.
unitone of stepsday (or the first step)Initially selected step unit.
presetscomma list of yesterday | last-week | last-month | last-quarter | last-year | 7d | 30d | 90d | 1yyesterday, last-week, last-month, last-quarter, last-yearBuilt-in presets of the Jump to menu, resolved against the current date when the menu opens (weeks follow the locale's first day). The presets property also accepts custom presets.
paramstringURL search parameter that holds the moment (e.g. asof). Read on first connection (a deep link wins over value), written after every travel, removed when live; popstate travels with reason "history".
param-modereplace | pushreplacereplace keeps one history entry; push adds an entry per travel so Back/Forward walk through visited moments.
frameCSS selectorData regions to mark while in the past: data-as-of-past (tinted outline + an “As of …” tag drawn with ::before), aria-busy during a pending travel and aria-describedby pointing to the banner text. They get position: relative (zero specificity, your styles win).
scopeCSS selectordocumentRoot in which [data-as-of-live-only] elements are governed, including ones added later. Set it when a page has several instances.
layoutstack | barstackstack: three stacked rows (sidebar, panel). bar: three columns for the top of a dashboard (from 46rem of container width, stacked below), banner under the bar.
localeBCP 47en-USDate, time, relative-time formatting and first day of the week.
labelstringPoint in timeAccessible name of the three-row group.
history-sizenumber5How many recent destinations the Jump to menu keeps (0 disables them).
storage-keystringlocalStorage key to persist recent destinations across visits (failures are ignored).
settlenumber (ms)400Quiet time after the last step click before a single travel is emitted. Typed dates settle after 900 ms, or at once on Enter or blur; Escape discards a typed draft.
data-statelive | pastSet by the component. data-pending is present while waitUntil promises are pending, data-drafting while steps settle.
data-as-of-live-onlyboolean (on your elements)Elements in scope with this attribute become inert (plus data-as-of-locked for styling, dimmed by default) while viewing the past; their previous inert state is restored when you return or when the component is removed.

Properties

NameTypDescription
dateDate | nullThe viewed moment (end of day with precision="day"), null when live (read-only).
livebooleanTrue while viewing live data (read-only).
departed{ value, date, live, at } | nullWhere you were before the last jump (the Left from row); at is when you left (read-only).
recentstring[]Recent destinations as normalized values, newest first. Settable (e.g. from your server).
pendingbooleanTrue while the last travel waits for its waitUntil promises (read-only).
presetsstring | Array<{ label, date: Date | string | (now: Date) => Date }>A string mirrors the attribute; an array replaces the built-in presets (e.g. fiscal periods, release dates, “Before the migration”).
stringsPartial<Record<string, string>>Overrides for every visible text and announcement (viewing, now, departed, live, past, liveTitle, liveSub, endOfDay, bannerText ({date}), bannerNote, bannerLocked, returnToNow, frameLabel ({date}), jump, presets, recent, goBack, goBackTo ({date}), stepBack / stepForward ({unit}), unitDay…, presetLastQuarter…, announceTravel ({date}), announceLive, announceMin, announceError ({date}, {current})…). English defaults.

Methods

NameDescription
travel(target, { reason? })Travel to a Date, a value string, or null / "now" for live. Emits mv-travel (cancelable), applies the moment, waits for waitUntil promises, then emits mv-arrive. Returns Promise<boolean> (false if cancelled, unchanged, superseded or failed).
returnToNow()Travel to live data (reason "return").
goBack()Travel to the Left from moment (reason "back"); calling it twice toggles between two moments.
step(n = -1, unit?)Step n units from the viewed moment (live counts as now); consecutive steps settle into one travel (reason "step").

Events

NameDescription
mv-travelCancelable, before a jump. detail: { from, to (Date | null = live), fromValue, toValue, live, reason: "step" | "input" | "preset" | "recent" | "back" | "return" | "history" | "api", waitUntil(promise) }. Refetch here and pass the promise to waitUntil: frames get aria-busy and a progress line runs; a rejection rolls the moment, Left from and recent back. preventDefault() refuses the jump.
mv-arriveThe jump is complete (after waitUntil promises resolved). detail: { from, to, fromValue, toValue, live, reason }. The arrival is announced politely.
mv-travel-errorA waitUntil promise rejected and the jump was rolled back. detail: { from, to, fromValue, toValue, reason, error }.

CSS classes

NameDescription
mv-as-of-bannerPast-mode region: -banner-icon, -banner-text (-banner-lead with <time class="mv-as-of-banner-time">, -banner-note) and .mv-as-of-return.
mv-as-of-consoleThe labeled group of three .mv-as-of-row[data-row="viewing | now | departed"], each with -head (.mv-as-of-label), -moment (<time class="mv-as-of-time"> + -sub) and -action. The viewing row gets data-past.
mv-as-of-controlsViewing row controls: -stepper (.mv-as-of-step[data-dir] + .mv-as-of-input), .mv-as-of-unit, .mv-as-of-jump; .mv-as-of-state is the Live / Past badge (data-tone), .mv-as-of-progress the pending line.
mv-as-of-menuJump to menu (Popover API top layer): -menu-group, -menu-heading, .mv-as-of-option (aria-current="date" on the viewed moment) with -option-check, -option-label, -option-sub.

CSS variables

NameDefaultDescription
--mv-as-of-tintvar(--mv-warning)Past-mode color: banner, Viewing row edge, Past badge, frame outline and tag (also read by frames outside the component).
--mv-as-of-livevar(--mv-success)Live color: Live badge and the breathing Now marker.
--mv-as-of-label-width6.75remWidth of the row label column in the stack layout.

Accessibility

The three rows are a labeled group (label, “Point in time” by default) of three nested groups named by their visible labels (Viewing, Now, Left from), each exposing its moment in a real <time datetime> element, so a screen reader reads “Viewing, group, Tue, Jun 30, 2026, End of day · 3 months ago”. Every control is a native element reachable with Tab: back / forward step buttons named “Back 1 month” / “Forward 1 month” (aria-disabled rather than disabled at the limits, so focus is never lost), a labeled date or date-time field (Enter commits, Escape discards the draft), a labeled step-size select, the Jump to button (aria-expanded, aria-controls) and “Go back”, whose accessible name includes the destination (“Go back to Monday, August 31, 2026”). The Jump to menu opens with focus on the current moment, moves with arrow keys, Home and End (roving tabindex), marks the viewed moment with aria-current="date" and a check icon, and closes on Escape or outside click, returning focus to its button. Each arrival is announced once in a polite live region (“Now viewing data as of Tuesday, June 30, 2026.” / “Back to live data.”), as are clamps to the earliest data and failed fetches with their rollback; the Now clock updates silently. The past is never signaled by color alone: the banner is a named region (“Historical view”) with words, a <time> and a Return to now button, the badge says Live or Past with a distinct icon, and framed regions get an “As of …” tag plus aria-describedby pointing to the banner text and aria-busy while data reloads. Live-only controls become inert (out of the tab order and the accessibility tree) and the banner says editing is paused; if a control that had focus disappears, focus moves to the date field. Reduced motion (OS or data-motion="reduce"): the Now marker stops breathing and the loading line becomes static. Forced colors: borders use CanvasText and the past edge, frames and progress use Highlight.

Diese Seite wurde mit KI übersetzt. Übersetzungsfehler melden