Amount
A money or quantity value, laid out to scan down a column. This is CSS-first: your app formats the number (locale grouping, decimal precision, sign) and supplies the parts as markup — the component styles and aligns them, with tabular figures so every row lines up digit-for-digit. One component, driven by settings. Not for capturing a value — this is display only; entry is Money for currency or Quantity for a count.
The rule for this family: displaying a value is Amount's job; capturing one is Money's (currency) or Quantity's (count). One display component for every numeric value; two entry components, split by what the value is — and when it is neither a selectable currency nor a steppable count (a rate, a fixed-currency price, a percentage), a plain .bo-input--numeric is the whole answer: no fourth component, just the input modifier.
Money — symbol, grouping, minor units
View code
<span class="bo-amount">
<span class="bo-amount__currency">$</span>
<span class="bo-amount__value">18,940<span class="bo-amount__fraction">.50</span></span>
</span> __currency and __fraction are
quieted so the whole-number part reads first.
Currency as an ISO code (either side)
View code
<span class="bo-amount">
<span class="bo-amount__value">1,234.00</span>
<span class="bo-amount__currency">EUR</span>
</span> Source order decides prefix vs suffix — $
before, EUR/kr after. No modifier needed.
Custom / non-ISO currency
View code
<!-- an app-defined currency (points, credits, store scrip) works exactly
like ISO codes and symbols — __currency accepts any text -->
<span class="bo-amount"><span class="bo-amount__value">2,400</span><span class="bo-amount__currency">PTS</span></span>
<span class="bo-amount"><span class="bo-amount__currency">₡</span><span class="bo-amount__value">18.50</span></span> <!-- an internal "credit" symbol --> Loyalty points, internal credits, any app-defined
unit of value — __currency is plain text with no ISO allowlist.
Same component, same alignment, no special-casing needed.
Editable money — not .bo-amount
View code
<!-- Amount (.bo-amount) is a DISPLAY value — a <span>, not a form control;
there is no ".bo-amount--input" variant, on purpose (an <input> can't host
__currency/__fraction as nested markup and stay a single accessible value).
Editable money has two shapes:
currency SELECTABLE -> .bo-money (currency select + amount input as one field)
currency FIXED -> .bo-form-field + .bo-input--numeric, currency in the label -->
<div class="bo-form-field">
<label class="bo-form-field__label" for="am-unit-price">Unit price (USD)</label>
<input class="bo-input bo-input--numeric" type="number" id="am-unit-price" value="12.50" min="0" step="0.01" inputmode="decimal" />
</div> .bo-amount is read-only display; it's
a <span>, not a form control, and there's deliberately no
input variant of it (the nested __currency/__fraction
parts can't live inside a native <input> and still be one
accessible value). Editable money has two shapes: when the user picks the
currency, use the Money field —
a currency select and amount input working as one control, precision
following the currency. When the currency is fixed (this demo's unit
price), a plain .bo-input--numeric with the currency in the
label is all it takes — the same way Quantity
puts the unit in __unit text next to an editable control.
Precision — a setting, not a variant
View code
<!-- 0 decimals (whole units) -->
<span class="bo-amount"><span class="bo-amount__currency">¥</span><span class="bo-amount__value">56,000</span></span>
<!-- 2 decimals (money) -->
<span class="bo-amount"><span class="bo-amount__currency">$</span><span class="bo-amount__value">4,208.00</span></span>
<!-- 4 decimals (unit price / FX rate) -->
<span class="bo-amount"><span class="bo-amount__currency">$</span><span class="bo-amount__value">0.0725</span></span> Whole units, money, or 4-dp unit prices — same component; the digits come from your formatter.
Decimal precision is app/domain data, not a CSS rule — there's no
--decimals modifier, because "how many decimals does this currency
get" varies by currency, not by design choice. The complete per-currency table
(ISO 4217 minor units) lives on the Money
field's currency decimals reference — most apps already get this from an
Intl.NumberFormat call or a currency-master table, which is exactly
where it belongs (not this CSS layer).
Negative & credit — two-channel
View code
<!-- debit: parentheses OR a minus sign carry the meaning; red is the 2nd channel -->
<span class="bo-amount bo-amount--negative"><span class="bo-amount__currency">$</span><span class="bo-amount__value">(2,110.00)</span></span>
<span class="bo-amount bo-amount--negative"><span class="bo-amount__currency">$</span><span class="bo-amount__value">−730.25</span></span>
<!-- credit: a + sign carries the meaning; green is the 2nd channel -->
<span class="bo-amount bo-amount--positive"><span class="bo-amount__currency">$</span><span class="bo-amount__value">+9,320.75</span></span> The sign or parentheses live in the text, so
colourblind and screen-reader users get the meaning without the colour.
--negative/--positive only add the hue (both AA on surface
and canvas, CI-gated).
The same contract for dates
View code
<!-- two-channel: the badge carries the word, colour only adds hue -->
<span class="bo-cluster">
<span class="bo-u-tabular">Aug 10, 2026</span>
<span class="bo-badge bo-badge--danger">Overdue by 4 days</span>
</span> An overdue date is the same decision as a negative
amount: the meaning must survive without colour, so the word
Overdue belongs in the text. This guidance moved here when
.bo-date was deprecated — it was the only real decision that
component carried, and it is a rule about values, not about a
date-specific widget.
Block — right-aligned totals on cards
--block right-aligns a standalone amount
— card totals, summary panels. Inside a table you don't need it: the cell's
__col--numeric already aligns the column.
Unit of measure
View code
<!-- quantity × unit -->
<span class="bo-amount"><span class="bo-amount__value">4</span><span class="bo-amount__unit">× EA</span></span>
<!-- rate per unit -->
<span class="bo-amount"><span class="bo-amount__currency">$</span><span class="bo-amount__value">12.50</span><span class="bo-amount__unit">/ kg</span></span>
<!-- unit price beside a quantity -->
<span class="bo-amount bo-amount--muted"><span class="bo-amount__currency">$</span><span class="bo-amount__value">3.10</span><span class="bo-amount__unit">/ pc</span></span> __unit carries × EA,
/ kg, pcs, hrs — a quantity’s unit or a rate’s
denominator. This is a read-only display value; for an editable count with
its own unit, see Quantity — same
__unit convention, plus increment/decrement and native
validation.
Mixed currencies in one table
| Invoice | Vendor | Amount |
|---|---|---|
| INV-3391 | Acme Supply Co. (US) | $4,208.00 |
| INV-3392 | Ling Freight Pte Ltd (SG) | SGD1,052.00 |
| INV-3393 | Nakamura Parts KK (JP) | ¥92,500 |
| INV-3394 | Al-Rashid Trading (BH) | BHD120.500 |
View code
<table class="bo-data-table">
<thead><tr><th>Invoice</th><th>Vendor</th><th class="bo-data-table__col--numeric">Amount</th></tr></thead>
<tbody>
<tr><td>INV-3391</td><td>Acme Supply Co. (US)</td><td class="bo-data-table__col--numeric"><span class="bo-amount"><span class="bo-amount__currency">$</span><span class="bo-amount__value">4,208.00</span></span></td></tr>
<tr><td>INV-3392</td><td>Ling Freight Pte Ltd (SG)</td><td class="bo-data-table__col--numeric"><span class="bo-amount"><span class="bo-amount__currency">SGD</span><span class="bo-amount__value">1,052.00</span></span></td></tr>
<tr><td>INV-3393</td><td>Nakamura Parts KK (JP)</td><td class="bo-data-table__col--numeric"><span class="bo-amount"><span class="bo-amount__currency">¥</span><span class="bo-amount__value">92,500</span></span></td></tr>
<tr><td>INV-3394</td><td>Al-Rashid Trading (BH)</td><td class="bo-data-table__col--numeric"><span class="bo-amount"><span class="bo-amount__currency">BHD</span><span class="bo-amount__value">120.500</span></span></td></tr>
</tbody>
</table> Right-alignment (__col--numeric) plus
tabular figures keep every row's digits stacked even though the
__currency affix text differs per row ($, SGD,
¥, BHD) — verified live, not assumed: the affix sits
outside the tabular-figure run, so a 3-letter code next to a 1-character symbol
doesn't shift where the digits land. No dedicated fixed-width affix column
needed for this case.
In a column — totals align, subtotal is strong
| Line item | Qty | Unit price | Amount |
|---|---|---|---|
| Standing desk | 4× EA | $1,052.00 | $4,208.00 |
| Monitor arm | 8× EA | $92.50 | $740.00 |
| Restocking fee | — | — | $(120.00) |
| Total | $4,828.00 |
View code
<div class="bo-data-table-container" tabindex="0">
<table class="bo-data-table">
<thead>
<tr><th>Line item</th><th class="bo-data-table__col--numeric">Qty</th><th class="bo-data-table__col--numeric">Unit price</th><th class="bo-data-table__col--numeric">Amount</th></tr>
</thead>
<tbody>
<tr>
<td>Standing desk</td>
<td class="bo-data-table__col--numeric"><span class="bo-amount"><span class="bo-amount__value">4</span><span class="bo-amount__unit">× EA</span></span></td>
<td class="bo-data-table__col--numeric"><span class="bo-amount bo-amount--muted"><span class="bo-amount__currency">$</span><span class="bo-amount__value">1,052.00</span></span></td>
<td class="bo-data-table__col--numeric"><span class="bo-amount"><span class="bo-amount__currency">$</span><span class="bo-amount__value">4,208.00</span></span></td>
</tr>
<tr>
<td>Monitor arm</td>
<td class="bo-data-table__col--numeric"><span class="bo-amount"><span class="bo-amount__value">8</span><span class="bo-amount__unit">× EA</span></span></td>
<td class="bo-data-table__col--numeric"><span class="bo-amount bo-amount--muted"><span class="bo-amount__currency">$</span><span class="bo-amount__value">92.50</span></span></td>
<td class="bo-data-table__col--numeric"><span class="bo-amount"><span class="bo-amount__currency">$</span><span class="bo-amount__value">740.00</span></span></td>
</tr>
<tr>
<td>Restocking fee</td>
<td class="bo-data-table__col--numeric">—</td>
<td class="bo-data-table__col--numeric">—</td>
<td class="bo-data-table__col--numeric"><span class="bo-amount bo-amount--negative"><span class="bo-amount__currency">$</span><span class="bo-amount__value">(120.00)</span></span></td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td class="bo-data-table__col--numeric"></td>
<td class="bo-data-table__col--numeric"></td>
<td class="bo-data-table__col--numeric"><span class="bo-amount bo-amount--strong"><span class="bo-amount__currency">$</span><span class="bo-amount__value">4,828.00</span></span></td>
</tr>
</tfoot>
</table>
</div> Amounts inherit the cell’s right-align
(__col--numeric); tabular figures keep the decimal points stacked.
--strong weights the total, --muted quiets the unit price.
Markup
<span class="bo-amount">
<span class="bo-amount__currency">$</span> <!-- symbol or ISO code, either side -->
<span class="bo-amount__value">18,940 <!-- your app formats grouping + precision -->
<span class="bo-amount__fraction">.50</span> <!-- optional: quiet the minor units -->
</span>
<span class="bo-amount__unit">/ kg</span> <!-- optional unit of measure -->
</span>
<!-- modifiers: --negative --positive --strong --muted --block --> Quick reference generated
| Class | Role |
|---|---|
| .bo-amount | block (root) |
| .bo-amount--block | variant: block |
| .bo-amount--muted | variant: muted |
| .bo-amount--negative | variant: negative |
| .bo-amount--positive | variant: positive |
| .bo-amount--strong | variant: strong |
| .bo-amount__currency | part: currency |
| .bo-amount__fraction | part: fraction |
| .bo-amount__unit | part: unit |
| .bo-amount__value | part: value |
API generated from CSS
| Blocks | bo-amount |
|---|---|
| Parts | bo-amount__currencybo-amount__fractionbo-amount__unitbo-amount__value |
| Variants | bo-amount--blockbo-amount--mutedbo-amount--negativebo-amount--positivebo-amount--strong |
| Component tokens | --bo-amount-affix-fg--bo-amount-fg |
| JS required | None — CSS-only. |
- CSS-first: your app formats the number (locale, grouping, precision, sign); the component styles the parts and aligns them with tabular figures.
- Negative/credit is two-channel — the sign or parentheses MUST be in the markup;
--negative/--positiveonly add colour. - Currency and unit are muted affixes; put the currency before or after the value by source order.
__currencyaccepts a symbol, an ISO 4217 code, or an app-defined currency (points, credits) — no allowlist or validation in CSS.- Decimal precision is per-currency domain data, not a CSS setting — there is no
--decimalsmodifier; your app (or anIntl.NumberFormatcall) decides and formats the digits before they reach this component.
Contrast — 17 AA pairs this component uses (generated, CI-gated)
| Text | on | Light | Dark |
|---|---|---|---|
| text-primary | bg-surface | 17.74 | 16.15 |
| text-primary | bg-canvas | 16.98 | 18.08 |
| text-muted | bg-surface | 7.56 | 6.65 |
| text-muted | bg-muted | 6.87 | 5.66 |
| bg-surface | text-primary | 17.74 | 16.15 |
| danger-text | danger-subtle | 5.91 | 8.06 |
| success-text | success-subtle | 4.79 | 10.17 |
| danger-text | bg-surface | 6.47 | 8.89 |
| danger-text | bg-canvas | 6.19 | 9.96 |
| success-text | bg-surface | 5.02 | 12.02 |
| success-text | bg-canvas | 4.8 | 13.46 |
| text-primary | bg-hover | 16.12 | 13.75 |
| text-primary | bg-selected | 17.01 | 11.86 |
| text-primary | bg-surface-raised | 17.74 | 14.51 |
| text-primary | bg-muted | 16.12 | 13.75 |
| success-text | bg-muted | 4.56 | 10.23 |
| danger-text | bg-muted | 5.88 | 7.57 |
Maturity generated
- Introduced
- v0.1.0
- CSS floor
- Chrome/Edge 99 · Firefox 97 · Safari 15.4
- Alignment scored
- 2026-08-21
- Assistive-tech evidence
- None recorded
Pin this component's first release with npm i @busy-office/ui@0.1.0.
The floor is derived from this component's stylesheet alone — set by
@layer — so it sits at or below the
whole framework's Chrome/Edge 119 · Firefox 129 · Safari 17.5.
No assistive-technology runtime result is recorded for any component: it needs a human listening to VoiceOver and NVDA on owner hardware; no gate in this repo can simulate announcement behaviour.
What automation covers instead is test:axe across every page at 1440 and 390, and check:forced-colors — see
accessibility.
Design-system alignment generated
100% (15/15) — how closely this component follows the design language. Each rule is scored 0-3 against a checkable citation; rules that don't apply leave the denominator. Scored 2026-08-21.
| Rule | Score | Evidence |
|---|---|---|
| Typography | 3 / 3 | both affixes are 0.875em of the VALUE they sit beside, and the comment says why a scale step would be wrong in one density and right in the other (94.13) |
| Colour | 3 / 3 | zero raw hex; negative/positive is the two-channel signature contract, AA-gated |
| Spacing | 3 / 3 | gap and margins all from spacing tokens |
| Interaction | n/a | a display span — no interaction surface; the input variant was deliberately refused |
| Content | 3 / 3 | opener carries the wrong-choice clause: "Not for capturing a value" — this is display only, and the clause names and links the two entry components instead (Money for currency, Quantity for a count) |
| Fit | 3 / 3 | display-only in every matrix context; adopted across the under-using screens (92.4) |