CurrencyBadge
- Status
- Implemented
- Family
- 1 of 13
- Registry
- @lopifi/currency-badge
npx shadcn@latest add @lopifi/theme @lopifi/currency-badgeThe component, rendering
Real data, the shipped component. Switch the theme to check the claims the spec makes below.
The four-layer spec
ISO code, optionally with a flag. Flags are regional, not linguistic — EUR gets the EU flag, and any currency without a clean single-country mapping gets no flag rather than a wrong one. Code is always present; the flag is decoration.
Lifted verbatim from Primitives. The doc and this page are the same paragraphs, so they cannot disagree.
What lands in your repository
One component is never one file. Everything below is copied into your project, where you own it.
- Files
components/ledger/currency-badge.tsx
- Lopifi dependencies · 1
- cn
- npm packages
- clsxtailwind-merge
- Registry entry
- /r/currency-badge.json
The whole component
No compiled bundle, no black box. This is the file the registry copies, and the file you will edit.
import * as React from "react";
import { cn } from "@/lib/ledger/cn";
/**
* Flags are regional, not linguistic. Any currency without a clean
* single-country mapping gets no flag rather than a wrong one.
*/
const FLAGS: Record<string, string> = {
AUD: "🇦🇺",
BRL: "🇧🇷",
CAD: "🇨🇦",
CHF: "🇨🇭",
CZK: "🇨🇿",
DKK: "🇩🇰",
EUR: "🇪🇺",
GBP: "🇬🇧",
HKD: "🇭🇰",
INR: "🇮🇳",
JPY: "🇯🇵",
KWD: "🇰🇼",
MXN: "🇲🇽",
NOK: "🇳🇴",
NZD: "🇳🇿",
PLN: "🇵🇱",
SEK: "🇸🇪",
SGD: "🇸🇬",
USD: "🇺🇸",
ZAR: "🇿🇦",
};
export interface CurrencyBadgeProps extends React.ComponentPropsWithoutRef<"span"> {
currency: string;
showFlag?: boolean;
size?: "sm" | "md";
}
/** The code is always present; the flag is decoration. */
export const CurrencyBadge = React.forwardRef<HTMLSpanElement, CurrencyBadgeProps>(
({ className, currency, showFlag = true, size = "sm", ...props }, ref) => {
const flag = showFlag ? FLAGS[currency] : undefined;
return (
<span
className={cn(
"inline-flex items-center gap-1.5 font-medium",
size === "sm" ? "text-label" : "text-body",
className,
)}
ref={ref}
{...props}
>
{flag ? (
<span aria-hidden="true" className="text-[1.1em] leading-none">
{flag}
</span>
) : null}
<span className="reference-numeric">{currency}</span>
</span>
);
},
);
CurrencyBadge.displayName = "CurrencyBadge";
export const CURRENCY_FLAGS = FLAGS;
Also in Primitives
- MoneyThe core primitive. A monetary value, formatted correctly.
- DeltaA signed change, with polarity and a redundant non-color cue.
- FigureA large display numeral with automatic size step-down.
- PercentPercent vs. percentage points, explicitly discriminated.
- RuleSingle / double / section accounting rule lines.
- StatusPillPayment and reconciliation states.
- SparklineInline trend, one-shot draw.
- TrendArrow + delta + optional sparkline, composed.
- AsOfTimestamp with live relative formatting and a stated timezone.
- RedactedMasked value with reveal.
- ReferenceCopyable transaction/account ID with disambiguated glyphs.
- EntityMarkBank/merchant logo with the full fallback ladder.
- TooltipHover/focus restatement. Never the only home of a fact.
- InfoTipThe ⓘ. Extra context that survives touch, because it opens on press.
See also
A component is specified in one place, defined in another, and running in a third. All three, from here.
Elsewhere in the system
Where CurrencyBadge sits in the rest of the system — the live section it renders in, the calculators that compute what it displays, and the ledger payloads it is fed from.
- Money and figure primitives, runningThe Primitives family, live. CurrencyBadge has no demo of its own there yet.Live
- PrimitivesBuild these first. Everything above them inherits their mistakes. Specs for 15 React components: Money, Delta, Figure, Percent and 11 more.Doc
- Currency decimal placesHow many decimals each ISO 4217 code is stored at, and where Intl disagrees.Doc
Components are copied into your repository and meant to be edited — rounding policy, fiscal calendar and compliance copy differ per company.
Told when Primitives changes
Components are copied into your repo, so a fix here does not reach you on its own. One email when a component in this family changes in a way that needs a migration — and nothing when it does not.