HextaUI

Prices for a storefront in any currency and locale. Handles sales, ranges, unit prices, free items and prices that change with the variant.

Open in new tab

Price is the one component every storefront block uses to show money. It takes a Money value, { amount: "48.00", currencyCode: "USD" }, and formats it with Intl for the shopper’s locale: $48.00 in the US, 48,00 € in Germany, ¥7,200 in Japan with no decimals, ₹4,000 with Indian grouping.

A compare-at price higher than the price shows struck through with the saving next to it, as a percentage or an amount. Screen readers hear “Sale price $36, Regular price $48” instead of two bare numbers. A range reads “From $28” on a product card or “$28 – $78” in full, and collapses to one price when both ends match. Zero reads Free, and a missing price reads Price unavailable or your own wording.

When the price changes, for example when a shopper picks a bigger size, only the digits that changed roll to their new value, with tabular figures so nothing around the price moves. Unit prices sit on their own line under the price.

The money helpers behind it, money, addMoney, multiplyMoney, sumMoney, fromMinorUnits and discountPercent, work in integer minor units so 0.1 + 0.2 is 0.30, and they refuse to add two currencies together. Cart, checkout and dashboard blocks use the same helpers.

  1. HextaUI Pro を入手する

    一度の支払いで、今後追加されるものも含めてすべてのブロックが使えます。料金を見る。

  2. Proレジストリをcomponents.jsonに追加する

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  3. トークンを追加する

    アカウントページでトークンを作成し、.env.local に HEXTAUI_PRO_TOKEN として設定してください。

  4. ブロックを追加する

    pnpm dlx shadcn@latest add @hextaui-pro/price

From your product data

Pass the price and compare-at price as Money, the same { amount, currencyCode } shape Shopify’s Storefront API returns. A compare-at price that isn’t higher is ignored, so a data mistake never shows a fake sale.

import { Price } from "@/components/blocks/price/price"
import type { Money } from "@/components/blocks/price/money"

type ProductPriceProps = {
  price: Money
  compareAtPrice: Money | null
}

export function ProductPrice({ price, compareAtPrice }: ProductPriceProps) {
  return (
    <Price
      size="lg"
      value={price}
      compareAt={compareAtPrice}
      locale="en-US"
      trailingZeros={false}
    />
  )
}

From minor units

Stripe, Paddle and most payment APIs send integers in the currency’s smallest unit. fromMinorUnits turns 4800 USD into $48.00 and 4800 JPY into ¥4,800, because yen has no cents.

import { fromMinorUnits } from "@/components/blocks/price/money"
import { Price } from "@/components/blocks/price/price"

type StripePrice = {
  unit_amount: number
  currency: string
}

export function PlanPrice({ price }: { price: StripePrice }) {
  return <Price value={fromMinorUnits(price.unit_amount, price.currency)} />
}

Unit prices

Show the price per 100 mL or per kg under the price, as EU rules require for goods sold by volume or weight. The money helpers do the math in minor units, so totals never drift.

import { fromMinorUnits, toMinorUnits, type Money } from "@/components/blocks/price/money"
import { Price } from "@/components/blocks/price/price"

export function RefillPrice({ price, milliliters }: { price: Money; milliliters: number }) {
  const per100 = fromMinorUnits(
    (toMinorUnits(price) * 100) / milliliters,
    price.currencyCode
  )
  return (
    <Price
      value={price}
      unitPrice={{ amount: per100, per: "100 mL" }}
      locale="de-DE"
    />
  )
}

構造

外側から内側へ組み合わせるパーツ。

パーツ説明
PriceThe current price, compare-at price, saving and unit price.
PriceSkeletonA loading placeholder the size of a price.
money, fromMinorUnits, toMinorUnitsBuild Money from major units, minor units or back.
addMoney, subtractMoney, multiplyMoney, sumMoney, compareMoneyExact money math in minor units. Throws when currencies differ.
formatMoney, discountPercentFormat Money as text, and work out the saving as a whole percent.

価格

Also takes span props.

プロパティ型デフォルト
valueThe price, or a range. null shows the unavailable label.
Money | { min: Money; max: Money } | null–
compareAtThe original price. Shown only when higher than value.
Money | null–
unitPriceA unit price line, such as $4.17 / 100 mL.
{ amount: Money; per: string }–
savingHow to show the saving on a sale.
"percent" | "amount" | "none""percent"
rangeShow a range as “From $28” or “$28 – $78”.
"from" | "span""from"
sizeText size. The compare-at price and saving scale with it.
"sm" | "default" | "lg" | "xl""default"
localeLocale for formatting. Fixed by default so server and browser render the same text.
Intl.LocalesArgument"en-US"
currencyDisplayHow to write the currency.
"symbol" | "narrowSymbol" | "code" | "name""narrowSymbol"
trailingZerosSet false to show $48 instead of $48.00. Prices with cents keep them.
booleantrue
animatedRoll changed digits when the price changes.
booleantrue
labelsWords for sale, regular, from, free, unavailable, save and per.
Partial<PriceLabels>–
プロパティ型デフォルト
amountA decimal string in major units, like "48.00".
string–
currencyCodeAn ISO 4217 code, like "USD".
string–
  • Sale prices read with their meaning, “Sale price $36, Regular price $48, Save 25%”, and a range reads “$28 to $78”.
  • Rolling digits are hidden from screen readers, which get the formatted price as plain text. With reduced motion, the price changes without rolling.
  • Figures are tabular, so a changing price never shifts the layout around it.

使用技術

Price を構成する無料のHextaUIコンポーネントです。それぞれ単独でインストールできます。

コード

4 個のファイルを components/blocks/price に追加しました。