# Atoms — Price

A **product price**: an optional prefix ("From"), the amount, a struck-through original price when
discounted, and an optional **monthly rate** line with an info button. Built from Gazelle's
**Tailwind v4 utilities**. The copy field on the Atoms → Price screen emits this exact markup. A
framework-free `.gz-price` API in `components/price.css` mirrors the same design for projects not
on Tailwind.

## Metadata

- **Category:** Atoms
- **Status:** Stable
- **Source:** `docs/atom-classes.js` (`GZ_ATOM_CLASSES.price` + `GZ_ATOM_MARKUP.price`, canonical
  markup); `components/price.css` (`.gz-price` framework-free compatibility API)
- **Production source:** `gazelle-int-website/app/components/BasePrice.vue` (staging, `e1acfea4`)

## When to use

- The price on a product or model card (Bike card, Model list card, Compare bike card).
- When the product has a finance or lease offer, show the monthly line. Its info button opens the
  payment-options dialog (production: `MonthlyPrice` modal).

## When not to use

- Not for a checkout total or a price table. This atom is a teaser price.
- Not on a dark surface. Colours are for light surfaces only. The Hero has its own inverse price
  treatment (`specs/blocks/hero-block.md`).
- Don't format amounts inside the atom. Pass locale-formatted strings (production uses
  `$n(…, 'currencyNoCents')`, e.g. "€ 2.399").

## Anatomy

```html
<div class="flex flex-col gap-2 tablet:gap-2.5">
  <p class="text-fg-primary type-body-lg tablet:type-h4-sm">
    From <s class="text-fg-muted line-through"><span class="sr-only">Was </span>€ 2.699</s>
    <span class="font-medium"><span class="sr-only">now </span>€ 2.399</span>
  </p>
  <p class="flex items-center gap-1 type-caption-sm text-fg-muted tablet:type-caption-lg">
    Per month from € 51,69
    <button type="button" class="<info>" aria-label="About monthly payments" data-gz-price-info>
      <svg class="size-4 shrink-0" …>…info-circle…</svg>
    </button>
  </p>
</div>
```

- **No discount** (final = base): the amount is plain text, `From € 2.399`.
- **No monthly rate**: leave out the second `<p>`.
- **Info button**: `data-gz-price-info` is the hook for the dialog. `components/product-card.js`
  turns a click into a `gz:price-info` event on the button (see `specs/molecules/bike-card.md`).

## Class lists

| Part | Utilities |
| --- | --- |
| **Root** | `flex flex-col gap-2 tablet:gap-2.5` |
| **Amount** | `text-fg-primary` + size |
| **Size — Small** (16px) | `type-body-sm` |
| **Size — Medium** (18px) | `type-body-lg` |
| **Size — Large** (20px) | `type-h4-sm` |
| **Step — `lg-tablet`** (→ Large from tablet) | `tablet:type-h4-sm` |
| **Was (struck)** | `text-fg-muted line-through` |
| **Now** | `font-medium` |
| **Monthly line** | `flex items-center gap-1 type-caption-sm text-fg-muted tablet:type-caption-lg` |
| **Info button** | `relative inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full text-fg-primary transition-colors duration-[120ms] before:absolute before:-inset-2 hover:text-fg-brand focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-page` |
| **Info icon** | `size-4 shrink-0` (Iconoir `info-circle`, stroke 1.5) |

## Tokens

- Text `text-fg-primary`, struck price and monthly line `text-fg-muted`, hover `text-fg-brand`,
  focus `ring-focus-ring`.
- Type: **Body Small / Body Large / H4 Small** for the amount, **Caption** for the monthly line,
  `font-medium` for the discounted price.

## Production mapping

| Part | Production | Design system | Decision |
| --- | --- | --- | --- |
| Amount (Bike card) | `text-md tablet:text-[20px] font-light` (18 → 20px) | Medium + `lg-tablet` step (`type-body-lg tablet:type-h4-sm`) | Exact sizes |
| Amount (Model list / Compare) | `!text-md` (18px) | Medium (`type-body-lg`) | Exact |
| Struck price | `line-through text-main-grey-700 font-light` | `text-fg-muted line-through` | Exact (`fg-muted` = grey-700) |
| Discounted price | `font-book ml-01` | `font-medium`, separated by a space | Book is Supreme's medium (500) |
| Monthly line | `mt-02 tablet:mt-[10px] gap-01 text-[12px] tablet:text-xs text-main-grey-700 font-light` | `gap-2 tablet:gap-2.5` on the root; `type-caption-sm tablet:type-caption-lg text-fg-muted gap-1` | Exact |
| Info icon | `nuxt-icon info`, clickable icon (no button, no label) | A real `<button aria-label>` with a 32px hit area | Keyboard- and screen-reader-accessible |
| Markup | `<span>` wrapping a `<div>` | `<div>` with two `<p>` | Valid HTML |
| Discount for screen readers | Two numbers read in a row | sr-only "Was … now …" | Improvement |
| Customer-group prices | `final_price` may be a per-group object, picked client-side | A single formatted `final` string | Data resolution stays in the product code |

## Cross-references

- `specs/foundations/typography.md` — the Body, H4 and Caption roles.
- `specs/molecules/bike-card.md` — the price in the card footer.
