エクスクルーシブ
As Of <mv-as-of>
過去を表示できるデータアプリ向けの時点(「as of」)コントロール。過去の数値が現在の数値と取り違えられることはありません。
文化的リファレンス
バック・トゥ・ザ・フューチャー、ロバート・ゼメキス(1985年、映画)。タイムマシンのダッシュボードには、行き先の時刻、現在の時刻、最後に出発した時刻という3つの表示が常に示されるため、旅行者は出発地点との関係で自分がいつにいるのかを見失いません。UI では、データアプリが同じ3つの行(表示中の時点、ライブの現在、直前のジャンプ前にいた時点で、ワンクリックで戻れるもの)を表示し、過去にいる間は落ち着いたバナー、枠で囲まれたデータ、ロックされた編集でそれを知らせます。
仕組み
過去を表示できるデータアプリ向けの時点(「as of」)コントロール。過去の数値が現在の数値と取り違えられることはありません。3つの行が常に表示されます:Viewing(選択した時点またはライブデータ。日付または日時のフィールド、月末を維持したまま進む ±1 時間/日/週/月/四半期/年のステップ、「End of last quarter」のようなプリセットと最近の移動先を含む Jump to メニュー付き)、Now(タイムゾーン付きのライブ時計)、Left from(直前のジャンプ前にいた時点。ワンクリックで戻れるため、2つの日付の比較は切り替えひとつで済みます)。過去を表示している間は、落ち着いたバナー(「You’re viewing data as of Jun 30, 2026. Return to now」)と、データ領域を囲む任意の枠が言葉でそれを伝え、スコープ内のすべての [data-as-of-live-only] コントロール(編集、調整、保存ボタン)は現在に戻るまで操作できなくなります。各ジャンプはキャンセル可能な mv-travel を経由し、その waitUntil(promise) によって再取得中は保留状態が表示され、取得に失敗した場合はアナウンス付きでロールバックされます。連続したステップは1回の移動にまとめられ、未来はライブに、min より前は利用可能な最も古いデータに制限されます。時点は URL パラメーターに保持でき(戻る/進むに対応)、最近の移動先は localStorage に保存できます。
| カテゴリー | ナビゲーション |
|---|---|
| タイプ | Web Component(<mv-as-of>) |
| ステータス | 安定版 |
| キット | パワーユーザー向けナビゲーション |
| 同時にインストールされるもの | button, input, select |
| Keywords | exclusive, 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
インストール
node scripts/add.mjs as-of --out ./src/marvelousMarvelous UI の MCP サーバーを使う AI エージェント:install_components({ slugs: ["as-of"], target_dir: "<absolute path>/src/marvelous", framework: "react" })。
コピーされるファイル(依存関係を含む):tokens/tokens.css, core/base.css, components/button/button.css, components/input/input.css, components/select/select.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。
使い方
クイックスタート、動作する最小限のマークアップ:
<mv-as-of frame="#report" min="2024-01-01"></mv-as-of>
<section id="report">…your figures…</section>基本となるマークアップです。ここから属性、data-*、CSS 変数でカスタマイズしてください:
<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 & revenue</h3>
<p>Harborline 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>API
Attributes
| Name | タイプ | Default | Description |
|---|---|---|---|
value | string ("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. | |
precision | day | minute | day | day: 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. |
min | date or date-time | Earliest moment with data (retention start, company founding). Stepping and typing clamp to it and the clamp is announced; the back step is disabled there. | |
steps | comma list of hour | day | week | month | quarter | year | day, week, month | Units 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. |
unit | one of steps | day (or the first step) | Initially selected step unit. |
presets | comma list of yesterday | last-week | last-month | last-quarter | last-year | 7d | 30d | 90d | 1y | yesterday, last-week, last-month, last-quarter, last-year | Built-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. |
param | string | URL 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-mode | replace | push | replace | replace keeps one history entry; push adds an entry per travel so Back/Forward walk through visited moments. |
frame | CSS selector | Data 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). | |
scope | CSS selector | document | Root in which [data-as-of-live-only] elements are governed, including ones added later. Set it when a page has several instances. |
layout | stack | bar | stack | stack: 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. |
locale | BCP 47 | en-US | Date, time, relative-time formatting and first day of the week. |
label | string | Point in time | Accessible name of the three-row group. |
history-size | number | 5 | How many recent destinations the Jump to menu keeps (0 disables them). |
storage-key | string | localStorage key to persist recent destinations across visits (failures are ignored). | |
settle | number (ms) | 400 | Quiet 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-state | live | past | Set by the component. data-pending is present while waitUntil promises are pending, data-drafting while steps settle. | |
data-as-of-live-only | boolean (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
| Name | タイプ | Description |
|---|---|---|
date | Date | null | The viewed moment (end of day with precision="day"), null when live (read-only). |
live | boolean | True while viewing live data (read-only). |
departed | { value, date, live, at } | null | Where you were before the last jump (the Left from row); at is when you left (read-only). |
recent | string[] | Recent destinations as normalized values, newest first. Settable (e.g. from your server). |
pending | boolean | True while the last travel waits for its waitUntil promises (read-only). |
presets | string | 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”). |
strings | Partial<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
| Name | Description |
|---|---|
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
| Name | Description |
|---|---|
mv-travel | Cancelable, 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-arrive | The jump is complete (after waitUntil promises resolved). detail: { from, to, fromValue, toValue, live, reason }. The arrival is announced politely. |
mv-travel-error | A waitUntil promise rejected and the jump was rolled back. detail: { from, to, fromValue, toValue, reason, error }. |
CSS classes
| Name | Description |
|---|---|
mv-as-of-banner | Past-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-console | The 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-controls | Viewing 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-menu | Jump 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
| Name | Default | Description |
|---|---|---|
--mv-as-of-tint | var(--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-live | var(--mv-success) | Live color: Live badge and the breathing Now marker. |
--mv-as-of-label-width | 6.75rem | Width 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. Known limits: moments are local wall times of the viewer (no time-zone attribute); the native date field follows the browser's language rather than locale; while a travel waits for its waitUntil promises, framed regions are faded and aria-busy, so their small print is briefly below 4.5:1; without scope, every [data-as-of-live-only] of the document is governed, so set scope when a page has several instances.