Exclusivobeta
All-in Price <mv-all-in>
Exibição de preço honesta, com tudo incluído, que diz em voz alta o que o drip pricing esconde. Envolva qualquer preço (card de produto, reserva de hotel ou aluguel, venda de ingressos, pagamento de um SaaS) e declare o preço-base anunciado mais cada cobrança como filhos data-fee ou uma propriedade lines: taxas fixas ou percentuais, por unidade ou por pedido, impostos por região (sobre a base, o subtotal ou o total), entrega, sobretaxas de cartão, descontos, taxas únicas de adesão, valores a pagar depois (“at the property”). O destaque é sempre o total que o cliente vai realmente pagar (“$713.05 total for 3 nights”), com o preço anunciado em segundo plano (“Room rate $164/night · + $221.05 fees & taxes”); um painel expansível “What’s included · 4 charges” detalha tudo numa tabela de verdade com notas em linguagem simples, linhas de pagamento agora versus pagamento depois e o preço de renovação, enquanto os extras opcionais ficam à parte numa checklist própria e nunca vêm pré-adicionados. Os padrões de custo oculto são sinalizados ao lado do preço, e não nas letras miúdas: uma renovação mais cara que o preço promocional (“Then $62.03/mo after 3 months”), um preço por unidade com mínimo (“Minimum 3 seats: priced for 3”), valores cobrados anualmente, preços “a partir de” e taxas pagas depois. Os valores usam Intl.NumberFormat com arredondamento adequado a cada moeda (en-US / USD por padrão, qualquer localidade e moeda). O complemento <mv-all-in-list> torna justa uma lista de comparação: cada card mostra o total com tudo incluído, os itens se reordenam por total ou por preço anunciado (FLIP, foco mantido) e o menor total é marcado. Pensado para a virada de 2024-2025 em direção à exibição do preço total (regras contra junk fees e drip pricing); ele exibe o que o app declara e não torna, por si só, um checkout conforme a lei.
| Categoria | Exibição de dados |
|---|---|
| Tipo | Web Component (<mv-all-in>) |
| Status | beta |
| Kit | Confiança e privacidade |
| Keywords | exclusive, culture, price, pricing, total-price, all-in-pricing, fees, junk-fees, drip-pricing, taxes, vat, breakdown, checkout, booking, ticketing, subscription, renewal, e-commerce, comparison, currency, intl |
When to use
- A booking, ticketing or rental page must lead with the total including every mandatory fee and tax, not the nightly or per-ticket rate
- A subscription checkout has an intro price, a higher renewal, a per-seat minimum or annual billing that must be stated next to the price
- Offers from several sellers must be compared and sorted by what the customer will actually pay
- Taxes and currency depend on the buyer's region and optional add-ons must stay out of the total until the customer adds them
Avoid when
- Only a formatted price with a compare-at discount is needed and there are no fees or taxes to disclose → use Price instead
- Several subscription plans must be compared feature by feature with a monthly / yearly switch → use Pricing instead
- The shopper is editing a multi-item cart with promo codes, shipping choice and a checkout button → use Cart instead
Instalação
node scripts/add.mjs all-in --out ./src/marvelousAgente de IA com o servidor MCP do Marvelous UI: install_components({ slugs: ["all-in"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Arquivos copiados (dependências incluídas): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, components/all-in/all-in.js, components/all-in/all-in.css.
Uso
Início rápido, a menor marcação que funciona:
<mv-all-in base="164" quantity="3" unit="night">
<span data-fee="32" data-per="unit">Destination fee</span>
<span data-fee data-kind="tax" data-rate="17.49%">Hotel tax</span>
</mv-all-in>Marcação de referência, para usar como ponto de partida e personalizar com atributos, data-* e variáveis CSS:
<div id="ai-demo" style="width:min(100%,64rem);margin-inline:auto">
<style>
#ai-demo { display:grid; gap:1.25rem; align-content:start }
#ai-demo .ai-top { display:grid; grid-template-columns:minmax(0,1.25fr) minmax(0,1fr); gap:1.25rem; align-items:start }
#ai-demo .ai-card { display:grid; gap:1rem; min-width:0; padding:1.25rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface); box-shadow:var(--mv-shadow-sm) }
#ai-demo .ai-eyebrow { margin:0; color:var(--mv-fg-subtle); font-size:.6875rem; font-weight:600; letter-spacing:.06em; text-transform:uppercase }
#ai-demo .ai-title { margin:.125rem 0 0; font-size:1.0625rem; font-weight:650; letter-spacing:-.01em }
#ai-demo .ai-meta { margin:.25rem 0 0; color:var(--mv-fg-muted); font-size:.8125rem }
#ai-demo .ai-media { display:flex; gap:.875rem; align-items:flex-start }
#ai-demo .ai-thumb { flex:none; display:grid; place-items:center; width:4.5rem; height:4.5rem; border-radius:var(--mv-radius-lg); background:linear-gradient(135deg, color-mix(in oklab, var(--mv-accent) 22%, var(--mv-bg-muted)), var(--mv-bg-muted)); color:var(--mv-fg-muted) }
#ai-demo .ai-thumb svg { width:1.75rem; height:1.75rem }
#ai-demo .ai-controls { display:flex; flex-wrap:wrap; align-items:center; gap:.625rem 1rem; padding-top:1rem; border-top:1px solid var(--mv-border) }
#ai-demo .ai-control { display:grid; gap:.3125rem }
#ai-demo .ai-label { color:var(--mv-fg-muted); font-size:.75rem; font-weight:500 }
#ai-demo .ai-log { min-height:1.1rem; margin:0; color:var(--mv-fg-subtle); font:.75rem/1.4 var(--mv-font-mono) }
#ai-demo .ai-compare { display:grid; gap:1rem; padding:1.25rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-bg-subtle) }
#ai-demo .ai-compare-head { display:flex; flex-wrap:wrap; align-items:flex-end; justify-content:space-between; gap:.75rem 1.25rem }
#ai-demo .ai-compare-tools { display:flex; flex-wrap:wrap; gap:.625rem 1rem }
#ai-demo .ai-offer { display:grid; grid-template-columns:minmax(0,13rem) minmax(0,1fr) auto; gap:.75rem 1.25rem; align-items:start; padding:1rem 1.125rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-lg); background:var(--mv-surface) }
#ai-demo .ai-seller { display:grid; gap:.25rem; min-width:0 }
#ai-demo .ai-seller h4 { margin:0; font-size:.9375rem; font-weight:650 }
#ai-demo .ai-seller p { margin:0; color:var(--mv-fg-muted); font-size:.75rem; line-height:1.45 }
#ai-demo .ai-offer .mv-button { align-self:start }
@media (max-width:52rem) {
#ai-demo .ai-top { grid-template-columns:minmax(0,1fr) }
#ai-demo .ai-offer { grid-template-columns:minmax(0,1fr) }
}
</style>
<div class="ai-top">
<!-- Hotel booking: the total leads, the nightly rate is secondary, the destination fee is due at the property. -->
<section class="ai-card" aria-labelledby="ai-hotel-title">
<div class="ai-media">
<span class="ai-thumb" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21V8l9-5 9 5v13"/><path d="M9 21v-6h6v6"/><path d="M8 10h.01M12 10h.01M16 10h.01"/></svg></span>
<div>
<p class="ai-eyebrow">Hotel · Chicago, IL</p>
<h3 class="ai-title" id="ai-hotel-title">The Marlowe River North</h3>
<p class="ai-meta">Deluxe King · Wed, Oct 14 to Sat, Oct 17, 2026 · 2 guests</p>
</div>
</div>
<mv-all-in id="ai-hotel" base="164" quantity="3" unit="night" base-label="Room rate" data-size="lg">
<span data-fee="32" data-per="unit" data-due="at the property" data-note="Wi-Fi, fitness center and two welcome drinks. Charged by the hotel.">Destination fee</span>
<span data-fee="18.90" data-note="Charged by the booking site to process your reservation.">Service fee</span>
<span data-fee data-kind="tax" data-rate="17.49%" data-note="State, county and city hotel taxes.">Chicago hotel tax</span>
<span data-fee="28" data-per="unit" data-optional data-note="Served 7-10:30 a.m. in the lobby café.">Breakfast for two</span>
<span data-fee="72" data-per="unit" data-optional data-note="In-and-out privileges.">Valet parking</span>
<span data-fee="40" data-optional data-note="Subject to availability, confirmed at check-in.">Late checkout (2 p.m.)</span>
</mv-all-in>
<div class="ai-controls">
<div class="ai-control">
<span class="ai-label" id="ai-nights-l">Nights</span>
<mv-segmented id="ai-nights" aria-labelledby="ai-nights-l" name="nights" value="3">
<button value="2">2</button>
<button value="3">3</button>
<button value="5">5</button>
</mv-segmented>
</div>
<div class="ai-control">
<span class="ai-label" id="ai-head-l">Headline</span>
<mv-segmented id="ai-head" aria-labelledby="ai-head-l" name="headline" value="total">
<button value="total">All-in total</button>
<button value="base">Base price</button>
</mv-segmented>
</div>
<p class="ai-log" id="ai-log" aria-live="polite" style="flex:1 1 12rem;align-self:end">Open “What’s included” or add an extra.</p>
</div>
</section>
<!-- SaaS checkout: intro price, higher renewal, per-seat minimum, tax and currency by region. -->
<section class="ai-card" aria-labelledby="ai-saas-title">
<div>
<p class="ai-eyebrow">Checkout · Team plan</p>
<h3 class="ai-title" id="ai-saas-title">Relaydesk CRM</h3>
<p class="ai-meta">Shared inbox, pipelines and reporting for small sales teams.</p>
</div>
<mv-all-in id="ai-saas" base="12" renewal="19" intro-periods="3" unit="seat" period="month" quantity="2" min-quantity="3" region="US-NY" base-label="Intro price">
<span data-fee data-kind="tax" data-rate="8.875" data-region="US-NY">New York sales tax</span>
<span data-fee data-kind="tax" data-rate="19" data-region="DE">German VAT</span>
<span data-fee data-kind="tax" data-rate="20" data-region="GB">UK VAT</span>
<span data-fee="2" data-per="unit" data-optional data-note="10 GB extra per seat.">Extra file storage</span>
</mv-all-in>
<div class="ai-controls">
<div class="ai-control">
<span class="ai-label" id="ai-seats-l">Seats</span>
<mv-segmented id="ai-seats" aria-labelledby="ai-seats-l" name="seats" value="2">
<button value="2">2</button>
<button value="5">5</button>
<button value="12">12</button>
</mv-segmented>
</div>
<div class="ai-control">
<label class="ai-label" for="ai-region">Billing country</label>
<select class="mv-select" id="ai-region" data-size="sm">
<option value="US-NY" selected>United States (NY)</option>
<option value="US-OR">United States (OR)</option>
<option value="DE">Germany</option>
<option value="GB">United Kingdom</option>
</select>
</div>
</div>
</section>
</div>
<!-- Ticket comparison: every offer shows its all-in total, sorted by what you will actually pay. -->
<section class="ai-compare" aria-labelledby="ai-cmp-title">
<div class="ai-compare-head">
<div>
<p class="ai-eyebrow">2 tickets · Floor B, Row 12</p>
<h3 class="ai-title" id="ai-cmp-title">Lumen Arena · Sat, Nov 7, 2026, 8:00 p.m.</h3>
</div>
<div class="ai-compare-tools">
<div class="ai-control">
<span class="ai-label" id="ai-show-l">Show</span>
<mv-segmented id="ai-show" aria-labelledby="ai-show-l" name="show" value="total">
<button value="total">All-in totals</button>
<button value="base">Advertised prices</button>
</mv-segmented>
</div>
<div class="ai-control">
<span class="ai-label" id="ai-sort-l">Sort by</span>
<mv-segmented id="ai-sort" aria-labelledby="ai-sort-l" name="sort" value="total">
<button value="total">Total</button>
<button value="base">Advertised</button>
</mv-segmented>
</div>
</div>
</div>
<mv-all-in-list id="ai-list" sort="total" headline="total">
<article class="ai-offer" data-name="SeatHub">
<div class="ai-seller">
<h4>SeatHub</h4>
<p>Resale marketplace · delivered to your phone 48 h before the show</p>
</div>
<mv-all-in base="89" quantity="2" unit="ticket" data-size="sm">
<span data-fee data-rate="27%" data-note="Marketplace fee, charged per order.">Service fee</span>
<span data-fee="7.50" data-per="unit" data-note="Collected for the venue.">Facility fee</span>
<span data-fee="5.95">Order processing</span>
<span data-fee data-kind="tax" data-rate="9%">Amusement tax</span>
</mv-all-in>
<button type="button" class="mv-button" data-variant="outline" data-size="sm">Select</button>
</article>
<article class="ai-offer" data-name="TixPoint">
<div class="ai-seller">
<h4>TixPoint</h4>
<p>Verified resale · instant mobile transfer</p>
</div>
<mv-all-in base="104" quantity="2" unit="ticket" data-size="sm">
<span data-fee="11" data-per="unit">Service fee</span>
<span data-fee data-kind="tax" data-rate="9%">Amusement tax</span>
</mv-all-in>
<button type="button" class="mv-button" data-variant="outline" data-size="sm">Select</button>
</article>
<article class="ai-offer" data-name="Lumen Arena box office">
<div class="ai-seller">
<h4>Lumen Arena box office</h4>
<p>Official seller · pick up at will call or print at home</p>
</div>
<mv-all-in base="118" quantity="2" unit="ticket" data-size="sm">
<span data-fee="3" data-per="unit" data-note="Venue restoration fee.">Facility fee</span>
<span data-fee data-kind="tax" data-rate="9%">Amusement tax</span>
</mv-all-in>
<button type="button" class="mv-button" data-variant="outline" data-size="sm">Select</button>
</article>
</mv-all-in-list>
</section>
<script type="module">
const hotel = document.getElementById("ai-hotel");
const saas = document.getElementById("ai-saas");
const list = document.getElementById("ai-list");
const log = document.getElementById("ai-log");
const say = (text) => { log.textContent = text; };
document.getElementById("ai-nights").addEventListener("mv-change", (e) => { hotel.quantity = Number(e.detail.value); });
document.getElementById("ai-head").addEventListener("mv-change", (e) => { hotel.headline = e.detail.value; });
hotel.addEventListener("mv-breakdown-open", (e) => say(`mv-breakdown-open · total ${hotel.formattedTotal}`));
hotel.addEventListener("mv-extra-change", (e) => say(`mv-extra-change · ${e.detail.label} ${e.detail.added ? "added" : "removed"}`));
document.getElementById("ai-seats").addEventListener("mv-change", (e) => { saas.quantity = Number(e.detail.value); });
// Each billing country has its own currency and list price.
const regions = {
"US-NY": { currency: "USD", locale: "en-US", base: 12, renewal: 19 },
"US-OR": { currency: "USD", locale: "en-US", base: 12, renewal: 19 },
DE: { currency: "EUR", locale: "de-DE", base: 11, renewal: 18 },
GB: { currency: "GBP", locale: "en-GB", base: 10, renewal: 16 },
};
document.getElementById("ai-region").addEventListener("change", (e) => {
const r = regions[e.target.value];
Object.assign(saas, { currency: r.currency, locale: r.locale, base: r.base, renewal: r.renewal, region: e.target.value });
});
document.getElementById("ai-show").addEventListener("mv-change", (e) => { list.headline = e.detail.value; });
document.getElementById("ai-sort").addEventListener("mv-change", (e) => { list.sort = e.detail.value; });
</script>
</div>Referência cultural
A roupa nova do imperador, Hans Christian Andersen (1837, fábula). Todos fingem admirar roupas que não existem até que uma criança simplesmente diz em voz alta o que todos estão vendo. A interface faz o papel da criança: informa o total real e cada custo oculto ao lado do preço, em palavras simples, em vez de entrar no jogo de uma tarifa anunciada que todos sabem que não é a que vão pagar.
API
Attributes
| Name | Tipo | Default | Description |
|---|---|---|---|
base | number | 0 | Advertised base price per unit and per period (the rate drip pricing would show alone). Shown secondary to the total, or as the headline with headline="base". |
quantity | number | 1 | Units bought (nights, tickets, seats). Multiplies the base and every data-per="unit" line. |
min-quantity | number | Minimum billable units (per-seat minimums). The total is computed on max(quantity, min-quantity); a flag says “Minimum 3 seats: priced for 3” when the request is below it, “Minimum 3 seats” otherwise. | |
unit / units | string | Unit name, singular and plural (units defaults to unit + "s"): “$164/night”, “total for 3 nights”, “$89 × 2 tickets”. | |
period | day | night | week | month | year | string | Makes the price recurring: “$12/seat/mo”, headline “$39.19 /mo total”. | |
billed-every | number | 1 | Periods charged at once (12 with period="month" = billed annually). The headline becomes the amount charged (“billed annually”) and a flag gives the per-period equivalent. |
renewal | number | Base price per unit after the intro periods. The renewal total is computed with the same fees, taxes and quantity (one-time lines excluded) and flagged next to the price: “Then $62.03/mo after 3 months” (warning tone when higher), plus a “Then, from month 4” row in the breakdown. | |
intro-periods | number | Number of periods billed at the base (intro) price before renewal applies. 1 reads “after the first year”; 12 months too. | |
from | boolean | The price is a starting price: “From” precedes the headline, the sentence starts with “From” and a flag says options can raise it. | |
region | string | Buyer region code (e.g. US-NY, DE, GB). Lines with data-region apply only when it matches (a line region “US” also matches “US-NY”; several codes separated by spaces or commas; “*” always applies). | |
currency | ISO 4217 code | USD | Currency of every amount; rounding follows its minor units (JPY has none). |
locale | BCP 47 tag | en-US | Number formatting locale. Whole amounts drop the cents ($164), others keep them ($187.40). |
headline | total | base | total | What the big number shows. base keeps the total on the line right under it (“$713.05 total · incl. $221.05 fees & taxes”) and in the accessible sentence. Inherited from an enclosing <mv-all-in-list headline> when not set. |
base-label | string | Base price | Name of the advertised price in the secondary line and the breakdown (“Room rate”, “Intro price”, “Ticket price”). |
open | boolean | Breakdown expanded. Reflects the disclosure state; setting it opens or closes the panel and emits mv-breakdown-open / mv-breakdown-close. | |
data-size | sm | lg | Compact (list rows, cards) or large (booking summary) headline. Default is medium. | |
data-headline / data-lowest | state | Set by the component (styleable): the effective headline mode, and data-lowest on the price with the lowest total inside an <mv-all-in-list>. | |
sort (mv-all-in-list) | none | total | base | none | Reorders the list children by all-in total or by advertised base price, lowest first (stable, original order for ties); none restores the original order. |
headline (mv-all-in-list) | total | base | Headline mode for every contained price that doesn't set its own, so all cards compare on the same number. | |
highlight (mv-all-in-list) | lowest | none | lowest | Marks the price with the lowest total (data-lowest + a “Lowest total” chip) when the list has two items or more. |
Properties
| Name | Tipo | Description |
|---|---|---|
lines | Array<{ id?, label, amount?, rate?, kind?, per?, on?, optional?, once?, due?, note?, region? }> | Declared charges. Setting an array replaces the data-fee children; null goes back to them. Reading returns normalized copies. amount is a fixed amount (discounts may be negative), rate a percentage (8.875 = 8.875%); kind: fee | tax | delivery | surcharge | discount; per: order | unit; on: base | subtotal | total (defaults: tax → subtotal, surcharge → total, others → base). |
extras | string[] | Ids of the optional extras the customer added. Extras start unchecked; set it only to restore a choice the customer already made. |
total | number | All-in amount for the current selection (read-only, rounded to the currency's minor units). |
formattedTotal | string | The total formatted for the locale and currency (read-only). |
summary | object | Read-only computation: { currency, locale, region, quantity, requestedQuantity, cycles, base, baseUnit, fees, taxes, discounts, extras, total, dueNow, dueLater: [{ due, amount }], renewal, lines: [{ id, label, kind, amount, optional, due }] }. |
sentence | string | The accessible sentence used as the price's name (read-only). |
strings | Partial<Record<string, string>> | Overrides for every visible text and announcement, with {placeholders} (e.g. total, totalFor, basePrice, plusFeesTaxes, noFees, toggle, toggleCount, groupFees, groupTaxes, dueLater, thenRow, extras, extrasHint, flagRenewal, flagRenewalFirst, flagMinimumApplied, flagBilled, flagDue, flagFrom, lowest, sentence, including, announce, extraAdded). English defaults. <mv-all-in-list> has its own strings (listSorted, listUnsorted, listTotals, sortTotal, sortBase). |
items (mv-all-in-list) | Element[] | The list's current children in display order (read-only). |
Methods
| Name | Description |
|---|---|
toggle(force?) | Opens or closes the breakdown like the button (opening emits the cancelable mv-breakdown-open). Returns false if the opening was cancelled. |
refresh() | Re-reads the declarations and redraws. Changes to data-fee children and attributes are picked up automatically; this is for bulk edits or custom flows. On <mv-all-in-list>, re-sorts and re-marks after you insert items. |
Events
| Name | Description |
|---|---|
mv-breakdown-open | The breakdown is opening. Cancelable when triggered by the button or toggle(). detail: { total, summary }. |
mv-breakdown-close | The breakdown closed. detail: { total }. |
mv-extra-change | Cancelable, before an optional extra is added or removed by the customer. detail: { id, label, added, amount }. preventDefault() keeps the previous state (e.g. out of stock). |
mv-total-change | The total changed (quantity, region, currency, extras, declarations). detail: { total, previous, summary }. Not emitted on first render. |
mv-sort (mv-all-in-list) | The list was reordered after its sort changed. detail: { by, items }. |
Content structure
| Name | Description |
|---|---|
[data-fee] children | One element per charge, hidden: its text is the label, data-fee the fixed amount (empty with data-rate="8.875%" for a percentage), plus data-kind, data-per="unit", data-on, data-optional, data-once, data-due="at the property", data-note (plain-language explanation), data-region and data-id. |
CSS classes
| Name | Description |
|---|---|
mv-all-in-price | Price block: -lowest (chip), -headline (-from, -amount, -suffix; aria-hidden), -sub (-base, -base-label, -base-value, -delta[data-tone=fees|clear|extras|discount], -total-inline) and the visually hidden -sentence. |
mv-all-in-flags / mv-all-in-flag | List of hidden-cost flags; data-tone="warning" | "info", data-flag="renewal" | "minimum" | "billed" | "due" | "once" | "from". |
mv-all-in-toggle | Disclosure button: -toggle-label, -toggle-count, -chevron. |
mv-all-in-panel | Collapsible panel (data-open, inert while closed) with -panel-inner, the breakdown -table (tbody[data-group=base|fees|taxes|extras|discounts], -group, -item, -item-label, -item-note, -tag, -value, -total-row, -due-row, -then-row). |
mv-all-in-extras | Optional extras fieldset: -extras-legend, -extras-hint, -extras-list, -extra (label), -check, -extra-label, -extra-note, -extra-amount. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-all-in-flag | var(--mv-warning) | Tone of warning flags (renewal higher than intro, minimum applied) and of the renewal row. |
--mv-all-in-good | var(--mv-success) | Tone of “No added fees” and of the “Lowest total” chip. |
Accessibility
The visible price is aria-hidden and replaced for assistive technologies by one complete, visually hidden sentence that always starts with the total, whatever the headline mode: “$713.05 total for 3 nights, including $221.05 in fees and taxes; base price $164 per night” (“From”, extras added, discounts and billing cycle are woven in), so a screen reader never hears a base price without its total. Hidden-cost flags are a real list of plain text next to the price, each with an icon and words (color is never the only signal). “What’s included” is a native <button> with aria-expanded and aria-controls pointing to the panel; the collapsed panel is inert and hidden, Escape inside it collapses it and returns focus to the button. The breakdown is a real <table> with a caption, column headers, row headers per charge, rowgroup headers (Fees, Taxes, Extras you added) and a tfoot with the total, due-now / due-later rows and the renewal row; notes and tags (“Due at the property”, “minimum”) sit inside the row header so they are read with their charge. Optional extras are a <fieldset> with a legend and a hint (“Not included unless you add them.”), native checkboxes wrapped in their <label>, described by their note; they are never pre-checked. Total changes (quantity, region, extras) are announced politely once, debounced (“Breakfast for two added. Total now $843.46.”); inside <mv-all-in-list> the list announces instead (“Totals updated. Lowest: TixPoint, $250.70.” / “Sorted by total price, lowest first.”) and reordering keeps DOM order equal to visual order and restores focus to the element that had it. Keyboard: Tab reaches the button, the extras and nothing decorative; visible focus rings, forced-colors borders on flags and chips. Reduced motion (OS or data-motion="reduce"): the panel, total and list reorder change without animation.