Money Value
A read-only money value that formats integer minor units or decimal strings by currency and locale without floating-point calculation, with accounting and compact modes.
Preview
{{-- Symbol side and spacing follow the locale's own currency pattern. --}}
<div class="flex flex-wrap items-baseline gap-6">
<x-ui.money-value minor="129900" currency="EUR" locale="nl-NL" />
<x-ui.money-value amount="42.50" currency="USD" locale="en-US" />
<x-ui.money-value minor="4800" currency="JPY" locale="ja-JP" />
<x-ui.money-value amount="1299" currency="EUR" locale="fr-FR" />
</div>
Mode options
Size options
Installation
php artisan ui:add money-value
Registry contract
php artisan ui:add money-value
writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.
-
resources/views/components/ui/money-value.blade.php
- Registry dependencies
- None — installs on its own.
- Packages
-
composer: jml/brok:^0.2
Use with AI
A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.
# Brok UI: Money Value (`money-value`)
A read-only money value that formats integer minor units or decimal strings by currency and locale without floating-point calculation, with accounting and compact modes.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add money-value
```
## Usage
```blade
{{-- Symbol side and spacing follow the locale's own currency pattern. --}}
<div class="flex flex-wrap items-baseline gap-6">
<x-ui.money-value minor="129900" currency="EUR" locale="nl-NL" />
<x-ui.money-value amount="42.50" currency="USD" locale="en-US" />
<x-ui.money-value minor="4800" currency="JPY" locale="ja-JP" />
<x-ui.money-value amount="1299" currency="EUR" locale="fr-FR" />
</div>
```
## Props
- `amount` (decimal-string|null, default `null`) — A decimal string; it is never converted to a float.
- `minor` (integer-string|null, default `null`) — An integer in currency minor units; it takes precedence over amount.
- `currency` (ISO 4217 code, default `USD`) — Currency code and minor-unit scale.
- `locale` (locale, default `en-US`) — Locale for symbols and separators.
- `mode` (standard|accounting|compact, default `standard`) — Display mode.
- `emptyLabel` (string, default `—`) — Localized empty value label.
- `invalidLabel` (string, default `Invalid amount`) — Localized invalid value label.
- `size` (sm|md|lg|xl, default `md`) — Display size; lg/xl for a total or a price hero.
- `signed` (boolean, default `false`) — Colour a negative amount in the destructive tone.
## Use when
- Use to summarize, sequence, or present data so users can scan it quickly.
- Display a server-provided monetary value with an ISO 4217 currency.
## Avoid when
- Do not add display-only ornament when the user needs actionable structure or exact comparison instead.
- Use currency-input when a person must edit an amount.
- Do not use this component for billing, tax, conversion, or arithmetic.
## Anti-patterns
- Adding display ornament without informational value
## Rules
- Use the `<brok:money-value>` tag (or `<x-ui.money-value>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.
## Links
- Docs: https://brokui.dev/docs/components/money-value
- Registry JSON (files, props, contract): https://brokui.dev/r/open/money-value.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Examples
Long Content
{{-- A number is one unbreakable run; a tight cell truncates it instead of folding
the symbol onto its own line. Sizes: sm to xl. --}}
<div class="flex w-full max-w-md flex-col items-start gap-4">
<x-ui.money-value amount="123456789012345678901234567890.12" currency="EUR" locale="nl-NL" class="block max-w-xs truncate" />
<div class="flex flex-wrap items-baseline gap-6">
<x-ui.money-value amount="1299" currency="EUR" locale="nl-NL" size="sm" />
<x-ui.money-value amount="1299" currency="EUR" locale="nl-NL" size="md" />
<x-ui.money-value amount="1299" currency="EUR" locale="nl-NL" size="lg" />
<x-ui.money-value amount="1299" currency="EUR" locale="nl-NL" size="xl" />
</div>
</div>
{{-- standard | accounting (parentheses) | compact (1.2M); `signed` colours a loss. --}}
<div class="flex flex-wrap items-baseline gap-6">
<x-ui.money-value amount="-1234.50" currency="USD" mode="standard" />
<x-ui.money-value amount="-1234.50" currency="USD" mode="accounting" signed />
<x-ui.money-value amount="1250000" currency="USD" mode="compact" />
</div>
{{-- No amount, an invalid amount, and an RTL locale with a trailing symbol. --}}
<div class="flex flex-wrap items-baseline gap-6">
<x-ui.money-value />
<x-ui.money-value amount="not-a-number" />
<div dir="rtl"><x-ui.money-value minor="129900" currency="AED" locale="ar-AE" /></div>
</div>
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| amount | decimal-string | null | null | A decimal string; it is never converted to a float. |
| minor | integer-string | null | null | An integer in currency minor units; it takes precedence over amount. |
| currency | ISO 4217 code | USD | Currency code and minor-unit scale. |
| locale | locale | en-US | Locale for symbols and separators. |
| mode | standard | accounting | compact | standard | Display mode. |
| emptyLabel | string | — | Localized empty value label. |
| invalidLabel | string | Invalid amount | Localized invalid value label. |
| size | sm | md | lg | xl | md | Display size; lg/xl for a total or a price hero. |
| signed | boolean | false | Colour a negative amount in the destructive tone. |
Slots
Default Blade slot only.
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- Uses string operations for amount normalization and grouping.
- Renders semantic data with a normalized decimal value and currency attribute.
- Declares registry capability flags: a11y, authoredStateFixtures, responsive, rtl, darkMode, localized.
Guidance
Present data for rapid scanning.
Use when
- Use to summarize, sequence, or present data so users can scan it quickly.
- Display a server-provided monetary value with an ISO 4217 currency.
Avoid when
- Do not add display-only ornament when the user needs actionable structure or exact comparison instead.
- Use currency-input when a person must edit an amount.
- Do not use this component for billing, tax, conversion, or arithmetic.
Use instead
- Table for exact comparison
- Plain text for a single value
Anti-patterns
- Adding display ornament without informational value
- Anatomy
- Theming hooks
Accessibility
- Keyboard
- No component-owned keyboard interaction; native element behavior applies.
- Focus
none
- The visible value is text and never depends on color.
- Empty and invalid inputs have explicit localized labels.
- Semantic HTML and a stable
data-slotattribute for styling and scripting hooks. - Focus-visible rings use the
ringtoken, so keyboard focus is always visible. - Disabled and invalid states are conveyed to assistive tech, not by color alone.
- Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
- Labels go through
__()and layout uses logical properties (ms-*,text-start), so it mirrors underdir="rtl"— flip the preview to RTL to confirm. - Dark mode uses the same semantic tokens under the
darkclass; high contrast follows forced-color system tokens.
Livewire
Livewire can update this component through forwarded wire:* attributes.
Source
The exact, editable file ui:add writes
into your app. Previews render this same code; there are no preview-only components.
@props([
'amount' => null,
'minor' => null,
'currency' => 'USD',
'locale' => 'en-US',
'mode' => 'standard',
'emptyLabel' => '—',
'invalidLabel' => 'Invalid amount',
// sm | md | lg | xl display size (lg/xl for a total or a price hero).
'size' => 'md',
// Colour a negative amount in the destructive tone.
'signed' => false,
])
@php
$currency = strtoupper((string) $currency);
$locale = str_replace('-', '_', (string) $locale);
$mode = in_array($mode, ['standard', 'accounting', 'compact'], true) ? $mode : 'standard';
$size = in_array($size, ['sm', 'md', 'lg', 'xl'], true) ? $size : 'md';
$sizeClass = ['sm' => 'text-xs', 'md' => 'text-sm', 'lg' => 'text-lg font-semibold', 'xl' => 'text-2xl font-semibold tracking-tight'][$size];
$digits = in_array($currency, ['BHD', 'IQD', 'JOD', 'KWD', 'LYD', 'OMR', 'TND'], true)
? 3
: (in_array($currency, ['CLP', 'DJF', 'GNF', 'ISK', 'JPY', 'KMF', 'KRW', 'PYG', 'RWF', 'UGX', 'UYI', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'], true) ? 0 : 2);
$raw = $minor !== null ? (string) $minor : ($amount !== null ? (string) $amount : null);
$valid = $raw !== null
&& strlen($raw) <= 128
&& preg_match('/^[A-Z]{3}$/', $currency) === 1
&& preg_match('/^-?\d+(?:\.\d+)?$/', $raw) === 1;
$decimal = null;
$formatted = null;
if ($valid) {
$negative = str_starts_with($raw, '-');
$unsigned = ltrim($raw, '-');
if ($minor !== null) {
$unsigned = ltrim($unsigned, '0') ?: '0';
$unsigned = str_pad($unsigned, $digits + 1, '0', STR_PAD_LEFT);
$whole = $digits === 0 ? $unsigned : substr($unsigned, 0, -$digits);
$fraction = $digits === 0 ? '' : substr($unsigned, -$digits);
} else {
[$whole, $fraction] = array_pad(explode('.', $unsigned, 2), 2, '');
$whole = ltrim($whole, '0') ?: '0';
$fraction = substr(str_pad($fraction, $digits, '0'), 0, $digits);
}
$decimal = ($negative ? '-' : '').$whole.($digits > 0 ? '.'.$fraction : '');
$numberFormatter = class_exists(\NumberFormatter::class)
? new \NumberFormatter($locale, \NumberFormatter::DECIMAL)
: null;
$currencyFormatter = class_exists(\NumberFormatter::class)
? new \NumberFormatter($locale, \NumberFormatter::CURRENCY)
: null;
$group = $numberFormatter?->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL) ?: ',';
$decimalSeparator = $numberFormatter?->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL) ?: '.';
// The symbol belongs to the requested currency, not to the locale's own
// default one: en-US with EUR must read "€", not "$".
$currencyFormatter?->setTextAttribute(\NumberFormatter::CURRENCY_CODE, $currency);
$symbol = $currencyFormatter?->getSymbol(\NumberFormatter::CURRENCY_SYMBOL) ?: $currency;
$grouped = preg_replace('/\B(?=(\d{3})+(?!\d))/', $group, $whole);
$displayNumber = $grouped.($digits > 0 ? $decimalSeparator.$fraction : '');
if ($mode === 'compact' && strlen($whole) >= 4) {
foreach ([12 => 'T', 9 => 'B', 6 => 'M', 3 => 'K'] as $power => $suffix) {
if (strlen($whole) > $power) {
$head = substr($whole, 0, -$power);
$tail = substr($whole, -$power, 1);
$displayNumber = $head.($tail !== '0' ? $decimalSeparator.$tail : '').$suffix;
break;
}
}
}
// Symbol side AND spacing follow the locale's own currency pattern:
// "€ 1.299,00" in nl-NL, "$42.50" in en-US, "1 299,00 €" in fr-FR.
$sample = $currencyFormatter?->formatCurrency(0, $currency) ?: null;
$symbolFirst = $sample === null || mb_strpos($sample, $symbol) <= mb_strpos($sample, '0');
$spaced = $sample === null || preg_match('/\p{Zs}/u', $sample) === 1;
$joiner = $spaced ? "\u{00A0}" : '';
$formatted = $negative
? ($mode === 'accounting' ? '(' : '-')
: '';
$formatted .= $symbolFirst
? '<span data-slot="money-value-symbol" class="text-muted-foreground">'.e($symbol).'</span>'.$joiner.e($displayNumber)
: e($displayNumber).$joiner.'<span data-slot="money-value-symbol" class="text-muted-foreground">'.e($symbol).'</span>';
$formatted .= ($negative && $mode === 'accounting') ? ')' : '';
}
$state = $raw === null ? 'empty' : ($valid ? 'valid' : 'invalid');
$toneClass = match (true) {
$state !== 'valid' => 'text-muted-foreground',
$signed && $negative => 'text-destructive-text',
default => 'text-foreground',
};
@endphp
{{-- One unbreakable run: the symbol never wraps away from its number. A tight
cell truncates it (pass `class="block truncate"`) rather than folding it. --}}
<data
data-slot="money-value"
data-mode="{{ $mode }}"
data-currency="{{ $currency }}"
data-state="{{ $state }}"
data-size="{{ $size }}"
@if ($valid && $negative) data-negative="true" @endif
@if ($decimal !== null) value="{{ $decimal }}" @endif
{{ $attributes->merge(['class' => 'inline-block min-w-0 whitespace-nowrap tabular-nums '.$sizeClass.' '.$toneClass]) }}
>@if ($state === 'valid'){!! $formatted !!}@else{{ __($state === 'empty' ? $emptyLabel : $invalidLabel) }}@endif</data>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.1.3 - Status
- Stable
- License
-
open - Accessibility reviewed
- No review date recorded
- Last breaking change
- No date recorded
- Deprecation
- Not deprecated
- Contract
-
v6 - Foundation
-
≥ 1.0.0