Price <mv-price>
Intl.NumberFormat으로 서식화한 전자상거래 가격(기본값 en-US / USD)입니다. 현재 가격, 취소선이 그어진 비교 가격, 자동 계산된 할인 배지(퍼센트 또는 금액), 단가, “From”, 가격 범위, 위 첨자 센트, 네 가지 크기, 그리고 호버와 포커스 시 표시되는 기준 통화 환산액(환율은 앱이 제공하며 절대 가져오지 않음)을 지원합니다. .mv-price-* 클래스로 정적 HTML로도 사용할 수 있습니다.
| 카테고리 | 데이터 표시 |
|---|---|
| 유형 | Web Component (<mv-price>) |
| 상태 | 안정 |
| Keywords | price, prix, sale, soldes, compare-at, discount, unit-price, currency, intl, e-commerce, exchange-rate, currency-conversion, reference-currency |
When to use
- A product price must be formatted for a locale and currency, with a struck compare-at price and discount badge
- A listing shows a price range, a From prefix or a unit price such as per ounce
- A subscription or quote shows a price with a note like per month or plus tax
- A store charges in one currency and visitors want an indicative equivalent in their own, at a rate the app provides
Avoid when
- Several subscription plans must be compared with features and a billing switch → use Pricing instead
- The figure is a KPI or stat that should animate between values → use Number Ticker instead
설치
node scripts/add.mjs price --out ./src/marvelousMarvelous UI MCP 서버를 사용하는 AI 에이전트: install_components({ slugs: ["price"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
복사되는 파일(의존성 포함): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, core/position.js, components/price/price.js, components/price/price.css.
사용법
기본 마크업입니다. 여기서 시작해 속성, data-*, CSS 변수로 커스터마이즈하세요:
<div id="price-demo" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,15rem),1fr));gap:1.25rem;width:min(100%,52rem);margin-inline:auto">
<style>
#price-demo .tile { display:grid; gap:.625rem; align-content:start; padding:1.25rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface) }
#price-demo .cap { margin:0; font-size:.75rem; font-weight:500; color:var(--mv-fg-muted); letter-spacing:.02em }
#price-demo .name { margin:0; font-size:.875rem; font-weight:500 }
</style>
<div class="tile" style="grid-column:1/-1">
<p class="cap">Product page · sale</p>
<p class="name" style="font-size:1.125rem">Merino crewneck sweater</p>
<mv-price id="price-demo-pdp" value="89" compare-at="119" note="+ tax" data-size="xl"></mv-price>
<div style="display:flex;gap:.5rem;flex-wrap:wrap">
<button class="mv-button" data-variant="outline" data-size="sm" data-price="89" aria-pressed="true">Sand · $89</button>
<button class="mv-button" data-variant="outline" data-size="sm" data-price="99">Midnight · $99</button>
<button class="mv-button" data-variant="outline" data-size="sm" data-price="119">Limited edition · $119</button>
</div>
</div>
<div class="tile">
<p class="cap">Grid thumbnail</p>
<p class="name">Glazed stoneware mug</p>
<mv-price value="24.9" compare-at="32" data-size="sm"></mv-price>
</div>
<div class="tile">
<p class="cap">Price per ounce</p>
<p class="name">Insulated bottle, 25 oz</p>
<mv-price value="34.5" unit-price="1.38" unit="oz"></mv-price>
</div>
<div class="tile">
<p class="cap">Price range</p>
<p class="name">Scented candle · 3 sizes</p>
<mv-price from value="18" max="46"></mv-price>
</div>
<div class="tile">
<p class="cap">Superscript cents</p>
<p class="name">Wave wireless headphones</p>
<mv-price value="189.9" compare-at="229.9" data-variant="superscript" data-size="lg" discount="amount"></mv-price>
</div>
<div class="tile">
<p class="cap">Other currency · de-DE</p>
<p class="name">Canvas tote bag</p>
<mv-price value="42" compare-at="56" currency="EUR" locale="de-DE"></mv-price>
</div>
<div class="tile">
<p class="cap">Reference currency · tooltip</p>
<p class="name">Leather weekender bag</p>
<mv-price value="249" compare-at="299" reference-currency="EUR" rate="0.8531" rate-note="Indicative rate, Oct 3" data-size="lg"></mv-price>
</div>
<div class="tile">
<p class="cap">Reference currency · inline</p>
<p class="name">Linen throw blanket</p>
<mv-price value="64" currency="GBP" locale="en-GB" reference-currency="USD" rate="1.3412" reference-display="inline"></mv-price>
</div>
<div class="tile">
<p class="cap">Range · equivalent in yen</p>
<p class="name">Ceramic pour-over set · 3 sizes</p>
<mv-price from value="38" max="72" reference-currency="JPY" rate="147.32"></mv-price>
</div>
<div class="tile">
<p class="cap">Static HTML (no JS)</p>
<p class="name">Opaline lamp</p>
<span class="mv-price" data-sale>
<span class="mv-price-current"><span class="mv-sr-only">Sale price: </span>$129</span>
<s class="mv-price-compare"><span class="mv-sr-only">Original price: </span>$159</s>
<span class="mv-price-discount">−19%</span>
</span>
</div>
</div>
<script type="module">
const root = document.getElementById("price-demo");
const pdp = root.querySelector("#price-demo-pdp");
root.addEventListener("click", (e) => {
const btn = e.target.closest("[data-price]");
if (!btn) return;
root.querySelectorAll("[data-price]").forEach((b) => b.setAttribute("aria-pressed", String(b === btn)));
pdp.value = Number(btn.dataset.price);
});
</script>API
Attributes
| Name | 유형 | Default | Description |
|---|---|---|---|
value | number | Current price (currency units, e.g. 24.9). | |
compare-at | number | Original price; greater than value = on sale (sale color, strikethrough price, badge). | |
max | number | Upper bound of a range (“$18 - $46”). | |
currency | string | USD | ISO 4217 code. |
locale | string | en-US | Formatting locale. auto follows the nearest [lang] (read at render), falling back to en-US. |
decimals | auto | 0 | 2 | auto | auto: “$89” but “$24.90”. |
discount | percent | amount | none | percent | Badge “−25%”, “−$30” or none. |
unit-price / unit | number / string | Unit price: “$1.38 / oz”. | |
from | boolean | “From” prefix. | |
note | string | Note (“+ tax”, “incl. VAT”, “/ month”). | |
label | string | Prefix read by screen readers when the price is not on sale. | |
reference-currency | string | ISO 4217 code of the equivalent (“≈ €82.40”). Shown only with a valid rate and a currency different from currency. | |
rate | number | Units of reference-currency for 1 unit of currency (USD to EUR: 0.9258). Supplied by the application; the component never fetches a rate. Applies to value and max. | |
rate-note | string | Extra line in the tooltip, such as the source or date of the rate (“Indicative rate, Oct 3”). | |
reference-display | tooltip | inline | tooltip | tooltip: the price gets a dotted underline and shows the equivalent and the rate on hover and keyboard focus. inline: muted “≈ €82.40” text after the price, no tab stop (use it inside a link or a button). |
data-size | sm | lg | xl | Size (md by default). | |
data-variant | superscript | Superscript cents ($189⁹⁰). | |
data-layout | stacked | inline | Stacked, or unit price on the same line. | |
data-tone | neutral | Keeps the normal color for a sale price. |
Properties
| Name | 유형 | Description |
|---|---|---|
formatted | string | Formatted current price (read-only). |
equivalent | string | Formatted equivalent in the reference currency, a range when max is set; empty without a usable rate (read-only). |
strings | Partial<Record<string, string>> | Overrides for the default text: from, salePrice, originalPrice, discount, approx (“≈ {amount}”, visible), approxLabel (“(about {amount})”, read by screen readers), rate (“1 {from} = {rate} {to}”). English defaults. Can be set before the element is defined. |
Methods
| Name | Description |
|---|---|
formatMoney(value, { currency, locale, decimals }) | Module export, reused by the other e-commerce components. |
moneyFormatter(options) / formatPercent(value, locale) | Exports: cached Intl formatter, signed percentage. |
formatEquivalent(value, { rate, currency, locale }) / convertMoney(value, rate) / isCurrency(code) | Exports: equivalent rounded to the digits of the target currency (none for JPY), empty string when the rate or code is not usable; raw conversion (NaN if unusable); ISO 4217 check. |
CSS classes
| Name | Description |
|---|---|
mv-price | Container; data-sale when on sale (set automatically by <mv-price>). |
mv-price-current / -compare / -discount / -unit / -from / -note / -fraction | Parts (generated or hand-written). |
mv-price-equivalent | Inline equivalent (reference-display="inline"). |
mv-price-tip / -tip-amount / -tip-rate / -tip-note | Tooltip bubble (top layer, Popover API) and its lines. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-price-sale-color | softened danger | Sale price color. |
--mv-price-tip-bg / --mv-price-tip-fg | inverted gray | Tooltip colors. |
Accessibility
The strikethrough price is an <s> prefixed with hidden “Original price:” text, the current price is prefixed with “Sale price:” when discounted, and the badge with “Discount:”. Screen readers never have to interpret a visual strikethrough. Reference currency: the equivalent is always in the accessibility tree as hidden text inside the price (“(about €82.40)”), so it is read in browse mode without hovering; in tooltip mode the price becomes a tab stop with a dotted underline, the tooltip (role=tooltip, top layer) opens on hover after 200 ms and immediately when the price receives focus (keyboard, or a tap in browsers that focus it), stays open while the pointer moves onto it, closes with Escape, and its rate line is the price's aria-describedby. Its visible amount is aria-hidden to avoid a double reading. Inside a link or a button, use reference-display="inline" to avoid a nested tab stop. Value change (variant): subtle fade, removed with reduced motion.