Dots

Money Amount

Displays an amount of money with smaller cents, a real minus sign and a masked state.

Balance$12,480.50
Jordan Lee+$250.00
Coffee Point-$4.75
Grocery Market-$62.18
import { MoneyAmount } from "@/components/ui/money-amount"

export function MoneyAmountDemo() {

MoneyAmount is the one way to show money across the kit: balances, transaction amounts, fees and rates. It formats the value with Intl.NumberFormat, sets the whole part at full size and the cents smaller, uses tabular digits and a true minus sign, and gives screen readers the full amount as one phrase. It only displays a value. To let people type an amount, use AmountField. Use NumberField for stepped limits.

Installation

pnpm dlx shadcn@latest add @dots/money-amount

Usage

import { MoneyAmount } from "@/components/ui/money-amount"
<MoneyAmount value={1250.5} />

Features

  • Locale-aware formatting through Intl.NumberFormat, in USD by default
  • Smaller fraction, tabular digits and a real minus sign (−)
  • Six sizes, including inline for amounts inside a sentence
  • Plus sign for incoming money with signDisplay="always"
  • Masked state that hides the digits and reads "Amount hidden"
  • Struck amounts as a semantic <del>, read with a "was" prefix
  • Asset codes Intl has no currency for, such as USDT or BTC, through unit
  • Fraction control for whole amounts and unit prices

Sizes

Use size to pick sm, default, lg, xl or 2xl. Use inline to keep the size of the surrounding text, for example in a sentence or a badge.

$1,250.50$1,250.50$1,250.50$1,250.50$1,250.50

You sent $1,250.50 to Sam Carter.

import { MoneyAmount } from "@/components/ui/money-amount"

const sizes = ["sm", "default", "lg", "xl", "2xl"] as const

Sign

Negative values show a minus by default. Set signDisplay="always" to add a plus to incoming money, and "never" for totals where the direction is already in the label, such as "Spent this month".

Refund from Coffee Point+$4.75
Payment to Coffee Point-$4.75
Spent this month$1,840.20
import { MoneyAmount } from "@/components/ui/money-amount"

export function MoneyAmountSign() {

Masked

Set masked when the user hides their balance. The currency symbol stays, the digits become dots, and screen readers hear "Amount hidden" instead of the dots.

BalanceAmount hidden
"use client"

import * as React from "react"

Struck and muted

Set struck for an amount that no longer applies, such as an old fee next to the new one. It renders a <del> and reads struckLabel ("was") before the amount. Set tone="muted" for amounts that were not taken, such as a declined payment.

Transfer feewas $2.99$0.00
Card payment, declined-$89.00
import { MoneyAmount } from "@/components/ui/money-amount"

export function MoneyAmountStruck() {

Units and precision

Pass unit for assets without an ISO currency code. The value is formatted as a decimal with up to 8 fraction digits and the code follows it. Use maximumFractionDigits for unit prices such as $1.0016, and showFraction={false} for whole amounts.

Stablecoin245.8 USDT
Bitcoin0.0042 BTC
Rate per USDT$1.0016
Euro account€980
import { MoneyAmount } from "@/components/ui/money-amount"

export function MoneyAmountUnits() {
<MoneyAmount value={245.8} unit="USDT" />
<MoneyAmount value={1.0016} maximumFractionDigits={4} />
<MoneyAmount value={980} currency="EUR" showFraction={false} />

Accessibility

The visible digits are split into parts for styling and are hidden with aria-hidden. A visually hidden copy carries the full formatted amount, so screen readers read "$1,250.50" as one phrase, not as separate pieces.

  • A masked amount reads "Amount hidden", never the dots.
  • A struck amount reads its struckLabel first, for example "was $2.99", because screen readers rarely announce <del>. Change struckLabel when the context needs another word.
  • Color is never the only signal. The sign carries the direction, so keep signDisplay="always" for incoming money rather than relying on a tint.
  • Give the amount context in nearby text, such as the merchant or "Balance".

API Reference

MoneyAmount

Renders a <span> and accepts all of its props except children.

PropTypeDefaultDescription
valuenumber-The amount in major units, e.g. 1250.5 for $1,250.50.
currencystring"USD"ISO 4217 code. Cannot be combined with unit.
unitstring-A non-ISO asset code, e.g. "USDT". Formats a decimal followed by the code. Empty falls back to currency.
localestring"en-US"The locale used for formatting.
size"inline" | "sm" | "default" | "lg" | "xl" | "2xl""default"The type size. inline keeps the surrounding size.
signDisplay"auto" | "always" | "never""auto"always adds a plus to positive values. Zero never gets a sign.
showFractionbooleantrueShow the fraction. Ignored when either fraction digits prop is set.
minimumFractionDigitsnumber-Fewest fraction digits. Defaults to the currency's digits, or 0 in unit mode.
maximumFractionDigitsnumber-Most fraction digits. Defaults to the currency's digits, or 8 in unit mode.
maskedbooleanfalseReplace the digits with dots and read "Amount hidden".
struckbooleanfalseRender a <del> with the digits struck through.
struckLabelstring"was"Read before a struck amount.
tone"default" | "muted""default"muted for amounts that were not taken or are stale.
classNamestring-Additional classes to apply to the root element.