---
title: Price
description: Locale-aware currency display with compare-at, unit-price (Grundpreis) and delta indicators
visibility: guest
draft: false
---

# Price

A locale-aware price display component for e-commerce and SaaS interfaces. Supports currency formatting, strike-through compare-at prices (RRP / UVP / MSRP), unit prices (Grundpreis) for EU price-marking compliance, delta indicators, and size variants.

## Basic Usage

:::preview{title="Simple price"}
<x-wirekit::price :amount="29.99" currency="USD" />
:::

## Compare-At Price (RRP / UVP / MSRP)

Show the previous or recommended retail price as a strike-through alongside the current price. The strike-through value answers the question "what was this before?":

- **RRP** — Recommended Retail Price (UK)
- **UVP** — Unverbindliche Preisempfehlung (Germany)
- **MSRP** — Manufacturer's Suggested Retail Price (US)

The `base` prop carries this comparison value. It does **not** mean "base price" in the unit-price sense — see [Unit Price (Grundpreis)](#unit-price-grundpreis) below for that.

:::preview{title="Sale price with strike-through"}
<x-wirekit::row gap="lg">
    <x-wirekit::price :amount="19.99" :base="29.99" currency="USD" />
    <x-wirekit::price :amount="49" :base="79" currency="EUR" />
</x-wirekit::row>
:::

## Unit Price (Grundpreis)

For pre-packaged goods sold by weight, volume, length, or area, EU and UK price-marking law requires the per-unit price to be displayed alongside the selling price. In Germany this is the *Grundpreis* per [PAngV § 4](https://www.gesetze-im-internet.de/pangv_2022/__4.html); in the UK it is the *unit price* per the Price Marking Order 2004; the EU baseline is [Directive 98/6/EC](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A31998L0006).

Pass `unitPrice` (numeric, same currency as `amount`) and `unitMeasure` (the reference unit label) — the component renders them as `(€8.99 / L)` next to the main price, in the same currency, in the same field of vision.

:::preview{title="Bottle of olive oil — €4.49 / 500 ml"}
<x-wirekit::price :amount="4.49" :unit-price="8.98" unit-measure="L" currency="EUR" />
:::

:::preview{title="Bag of coffee beans — €12.99 / 250 g"}
<x-wirekit::price :amount="12.99" :unit-price="51.96" unit-measure="kg" currency="EUR" />
:::

:::preview{title="Sale price + unit price combined"}
<x-wirekit::price :amount="3.49" :base="4.99" :unit-price="6.98" unit-measure="L" currency="EUR" />
:::

::: warning
**Reference units are jurisdiction-dependent.** Germany / EU: per `kg`, `L`, `m`, `m²`. For nominal quantities ≤ 250 g / 250 ml the law allows `100g` / `100ml` instead. The UK rules mirror this. The US has no federal unit-price law but several states (NY, MA, CA, MI) require unit pricing in retail with their own unit conventions (`oz`, `lb`, `fl oz`). The component does not validate the unit string — pass whatever your jurisdiction requires.
:::

## Delta Indicator

Attach a percentage or absolute change indicator:

:::preview{title="Price with delta (percent)"}
<x-wirekit::row gap="lg">
    <x-wirekit::price :amount="24.99" currency="USD" :delta="-20" />
    <x-wirekit::price :amount="34.99" currency="USD" :delta="10" />
</x-wirekit::row>
:::

:::preview{title="Price with absolute delta"}
<x-wirekit::price :amount="120" currency="USD" :delta="-30" delta-format="absolute" />
:::

## Size Variants

:::preview{title="All size variants"}
<x-wirekit::stack gap="md">
    <x-wirekit::price :amount="9.99" currency="USD" size="xs" />
    <x-wirekit::price :amount="9.99" currency="USD" size="sm" />
    <x-wirekit::price :amount="9.99" currency="USD" size="md" />
    <x-wirekit::price :amount="9.99" currency="USD" size="lg" />
    <x-wirekit::price :amount="9.99" currency="USD" size="xl" />
</x-wirekit::stack>
:::

## Minor Units (Cents)

Pass amounts in minor units (cents) and let the component convert:

:::preview{title="Minor units conversion"}
<x-wirekit::price :amount="2999" currency="USD" :minor-units="true" />
:::

## With Suffix Slot

Add recurring interval text or other context:

:::preview{title="Price with suffix"}
<x-wirekit::price :amount="12" currency="USD" size="lg">
    <x-slot:suffix>/ month</x-slot:suffix>
</x-wirekit::price>
:::

## Full Sale Example

:::preview{title="Complete sale price"}
<x-wirekit::price :amount="49.99" :base="79.99" currency="USD" :delta="-38" size="lg">
    <x-slot:suffix>/ year</x-slot:suffix>
</x-wirekit::price>
:::

## Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `amount` | `int\|float` | `null` | The price amount to display |
| `currency` | `string` | `'USD'` | ISO 4217 currency code |
| `locale` | `string` | `app()->getLocale()` | Locale for number formatting |
| `base` | `int\|float` | `null` | Compare-at / RRP / UVP / MSRP price (shown as strike-through) |
| `unitPrice` | `int\|float` | `null` | Per-unit price (Grundpreis) — required by EU / UK price-marking law for goods sold by weight / volume / length / area |
| `unitMeasure` | `string` | `null` | Reference unit label paired with `unitPrice` (e.g. `L`, `kg`, `100g`, `100ml`, `m²`) |
| `delta` | `int\|float` | `null` | Change indicator value |
| `delta-format` | `string` | `'percent'` | Delta format: `percent` or `absolute` |
| `size` | `string` | `'md'` | Size variant: `xs`, `sm`, `md`, `lg`, `xl` |
| `minor-units` | `bool` | `false` | When true, divides amount / base / unitPrice by 100 |

## Slots

| Slot | Description |
|------|-------------|
| `suffix` | Text after the price (e.g. "/ month") |

## Accessibility

- Root `<span>` carries an `aria-label` combining all visible parts (amount, compare-at, delta, unit price)
- Strike-through compare-at price uses `<del>` element for semantic meaning
- `<bdi>` wraps formatted values for correct RTL rendering
- Unit price suffix carries `aria-hidden="true"` so the same information is not read twice — it is already woven into the wrapper's `aria-label`

## Keyboard Interaction

This component is purely presentational and does not respond to keyboard input.

## Pitfalls

- **Don't pass an unformatted number to `:value`.** The component formats with the active locale's decimal/thousand separators — passing `19.99` is correct; passing `"19,99"` (already formatted) double-formats.

## Design Tokens

| Token | Used for |
| --- | --- |
| `--font-wk-sans` | Price font family |
| `--font-wk-heading-weight` | Amount + currency weight |
| `--text-wk-xs` / `--text-wk-sm` / `--text-wk-md` / `--text-wk-lg` / `--text-wk-xl` | Font size per `size` prop |
| `--color-wk-text` | Amount + currency color |
| `--color-wk-text-muted` | Strike-through base price + suffix |
| `--color-wk-success-text` | Negative-direction delta (savings) |
| `--color-wk-danger-text` | Positive-direction delta (price increase) |
| `--color-wk-border` | Optional separator under base/strike row |
