PriceTag

Rounded-box price overlay for house cards, galleries, and buy-side comparison surfaces.

Quick Preview

N78,650,000

Usage

PriceTag is display only. It prints value and description as given and never composes a sentence, picks a format, or decides that a line is too small to be worth showing.

import { PriceTag } from "@giddaa-housing/ui/price-tag";

<PriceTag value="N78,650,000" size="md" />

Wording, currency conversion and number formatting all belong to the app, which is the only place that knows the locale, the product's voice, and what the second line is actually for.

Deprecated props

amount, shortAmount, format, monthlyPayment and monthlyPaymentLabel still work and keep their old behaviour exactly. They are removed in the next major — see Migrating.

Examples

Size

Use size from xxs through lg to match image density and overlay prominence.

N78,650,000Xxs
N78,650,000Xs
N78,650,000Sm
N78,650,000Md
N78,650,000Lg

Formatting

Format the number before you pass it. Intl.NumberFormat already knows the locale and the currency, and it is the app that knows how much room the card has.

₦78,650,000Full · ₦78,650,000
₦78.65MCompact · ₦78.65M

Description

description is a second line under the price, printed as given. It is a slot, not a monthly-payment field — the same space can carry financing, scarcity, or a fee note, and PriceTag has no opinion about which.

N78,650,000

No

Price only

N78,650,000Low as N1,650,000 monthly

Financing

Your wording

N78,650,0003 units left

Scarcity

Same slot, other job

Currency Dropdown

Pass currencies to turn the tag into a dropdown trigger. Each option is a currency, a value, and an optional description.

N78,650,000

No

Plain price pill

Yes

Chevron to switch currency

Open

Currency popover open

Selecting a row calls onCurrencyChange with the option. PriceTag does not hold the selection — pass selectedCurrency to say which row is current, so the tag stays in step with whatever the app decided.

Custom rows

renderMenuOption replaces a row's contents. PriceTag keeps the button, its selected state and its keyboard behaviour, so a custom row can't quietly lose them.

Options carry whatever else you need. PriceTag infers the option type from currencies, so fields it knows nothing about — a rate, an id, a country code — arrive in renderMenuOption and onCurrencyChange typed, without a cast or a generic to write out.

<PriceTag
  currencies={[{ currency: "USD", value: "$51,200", rate: 1535.16 }]}
  renderMenuOption={(option) => <span>{option.rate}</span>} // number
/>

Custom rows

Live rate from the option's own fields

Migrating

Everything below still works. Move at your own pace; the old props go in the next major.

OldNew
amount="N78,650,000"value="N78,650,000"
shortAmount + format="shortened"Format before passing, then value
monthlyPayment="N845"description="Low as N845 monthly"
monthlyPaymentLabel="From"Part of your description string
option { code, amount }option { currency, value }
option { monthlyPayment }option { description }

Two differences worth knowing when you move:

  • The monthly line was hidden below size="md". A description you pass deliberately renders at every size, because deciding a line isn't worth the room is the app's call.
  • The old props built the sentence for you — "Low as N845 monthly" on the tag, "As low as N845 per month" in the dropdown, which never quite matched. description is printed exactly as given, so write the line you want.

New props win over the ones they replace, so an option or a tag can be migrated one field at a time.

Tokens

SlotTokenNotes
Surfacebg-surface-brandTag background resolves green in Light and deep green in Dark.
Labeltext-fg-on-brandStays readable on either theme.
Popoverbg-surface-overlay, shadow-3Currency comparison panel uses overlay elevation.

Props

Prop/APITypeDefaultDescription
valueReactNode-The price, exactly as it should read.
descriptionReactNode-A second line under the price. Your words, printed as given.
size"xxs" | "xs" | "sm" | "md" | "lg""md"Tag scale. Also sets the corner radius, which grows with the size.
currenciesPriceTagCurrencyOption[]-Turns the tag into a dropdown trigger.
selectedCurrencystring-Which option's currency is current.
onCurrencyChange(option: PriceTagMenuOption) => void-Called when a row is selected.
renderMenuOption(option: PriceTagMenuOption) => ReactNode-Replaces a row's contents, keeping its button and behaviour.
open / defaultOpen / onOpenChangepopover state-Controls the dropdown.
popoverClassNamestring-Overrides on the popover surface.
classNamestring-Local layout or spacing overrides.
amountstring-Deprecated. Use value.
shortAmount / formatstring / "full" | "shortened""full"Deprecated. Format before passing.
monthlyPayment / monthlyPaymentLabelstring"Low as"Deprecated. Use description.
native button propsReact button props-Passed to the trigger, or to the static tag.

PriceTagCurrencyOption is { currency, value, description? }, plus the deprecated code, amount, shortAmount, monthlyPayment and monthlyPaymentLabel. PriceTagMenuOption<TOption> is that option after the deprecated fields have been folded in, with currency and value resolved and a selected boolean — the original object is spread in, so code still reading code or amount off it keeps working.

Both PriceTagMenuOption and PriceTagProps are generic over your option type and default to the base one, so existing code referring to them unparameterised keeps compiling. You rarely need to name the type parameter: it is inferred from currencies.

Accessibility

  • Plain tags render as non-interactive text.
  • Dropdown tags render as buttons and preserve Base UI popover keyboard behavior.
  • Keep the currency on each option explicit so screen reader users can distinguish rows. If renderMenuOption drops the code from the visible row, give the row an accessible name another way.
  • description is read out with the price, so keep it short and meaningful rather than decorative.

On this page