Skip to content
Brok UI

Loading…

No results

Price

Open source

A product price: the current amount, an optional struck-through compare-at amount and an optional savings badge in one baseline-aligned run, formatted by currency and locale through money-value.

Version
v1.0.1
Stability
stable
License
MIT
Related
Money Value
Product Card

Preview

On sale, with derived savings Sale price $118.00 Original price $148.00 Save 20%
Sizes
$96.00 $96.00 $96.00 $96.00
Locale, currency and a suffix
€ 1.299,00 / month ¥1,299 From $9 each
Size
previews.components.price.default.blade.php Blade
<div class="flex flex-col gap-6">
    <div class="flex flex-col gap-2">
        <span class="text-sm font-medium text-foreground">{{ __('On sale, with derived savings') }}</span>
        <x-ui.price amount="118" compare-at="148" show-savings size="lg" />
    </div>

    <div class="flex flex-col gap-2">
        <span class="text-sm font-medium text-foreground">{{ __('Sizes') }}</span>
        <div class="flex flex-wrap items-baseline gap-6">
            <x-ui.price amount="96" size="sm" />
            <x-ui.price amount="96" size="md" />
            <x-ui.price amount="96" size="lg" />
            <x-ui.price amount="96" size="xl" />
        </div>
    </div>

    <div class="flex flex-col gap-2">
        <span class="text-sm font-medium text-foreground">{{ __('Locale, currency and a suffix') }}</span>
        <div class="flex flex-wrap items-baseline gap-6">
            <x-ui.price amount="1299" currency="EUR" locale="nl-NL" :suffix="__('/ month')" />
            <x-ui.price amount="1299" currency="JPY" locale="ja-JP" />
            <x-ui.price :amount="__('From $9')" :suffix="__('each')" />
        </div>
    </div>
</div>
Sm Current
Gap Current
Text Current
Compare Current
Suffix Current
Md Current
Lg Current
Xl Current

Installation

terminal
php artisan ui:add price

Registry contract

php artisan ui:add price 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.

  • blade resources/views/components/ui/price.blade.php
Registry dependencies
money-value badge
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.

price.md
# Brok UI: Price (`price`)

A product price: the current amount, an optional struck-through compare-at amount and an optional savings badge in one baseline-aligned run, formatted by currency and locale through money-value.

Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.

## Install

```bash
php artisan ui:add price
```

## Usage

```blade
<div class="flex flex-col gap-6">
    <div class="flex flex-col gap-2">
        <span class="text-sm font-medium text-foreground">{{ __('On sale, with derived savings') }}</span>
        <x-ui.price amount="118" compare-at="148" show-savings size="lg" />
    </div>

    <div class="flex flex-col gap-2">
        <span class="text-sm font-medium text-foreground">{{ __('Sizes') }}</span>
        <div class="flex flex-wrap items-baseline gap-6">
            <x-ui.price amount="96" size="sm" />
            <x-ui.price amount="96" size="md" />
            <x-ui.price amount="96" size="lg" />
            <x-ui.price amount="96" size="xl" />
        </div>
    </div>

    <div class="flex flex-col gap-2">
        <span class="text-sm font-medium text-foreground">{{ __('Locale, currency and a suffix') }}</span>
        <div class="flex flex-wrap items-baseline gap-6">
            <x-ui.price amount="1299" currency="EUR" locale="nl-NL" :suffix="__('/ month')" />
            <x-ui.price amount="1299" currency="JPY" locale="ja-JP" />
            <x-ui.price :amount="__('From $9')" :suffix="__('each')" />
        </div>
    </div>
</div>
```

## Props

- `amount` (string|int|float|null, default `null`) — The current price. A numeric value ("118", "118.00", 118) is formatted by currency and locale through money-value; any other string is shown verbatim.
- `compareAt` (string|int|float|null, default `null`) — The original price on a sale, rendered struck through with a visually-hidden "Original price" prefix. Same numeric-or-verbatim rule as `amount`.
- `currency` (string, default `USD`) — ISO 4217 code handed to money-value for numeric amounts.
- `locale` (string, default `en-US`) — BCP 47 locale that decides symbol side, grouping and decimal separators.
- `size` (sm|md|lg|xl, default `md`) — Scale of the current amount; the compare-at sits one step below it.
- `showSavings` (bool, default `false`) — Appends a "Save N%" badge computed from two numeric amounts when compareAt exceeds amount.
- `suffix` (string|null, default `null`) — Muted trailing text such as "/ month" or "each".

## Use when

- Use to summarize, sequence, or present data so users can scan it quickly.
- Showing what a product costs on a card, a product page, a cart line or a sticky buy bar.
- A sale needs the original price struck through beside the current one, read correctly by AT ("Sale price … Original price …").
- You want the discount percentage derived rather than hard-coded: show-savings.

## Avoid when

- Do not add display-only ornament when the user needs actionable structure or exact comparison instead.
- Presenting a single money figure that is not a price (a balance, a total, a fee) — compose <x-ui.money-value> directly.
- Laying out plan tiers with a period and features — that is <x-ui.pricing-card>.

## Anti-patterns

- Adding display ornament without informational value

## Rules

- Use the `<brok:price>` tag (or `<x-ui.price>`) 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/price
- Registry JSON (files, props, contract): https://brokui.dev/r/open/price.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.blade.php Blade
<div class="max-w-xs">
    <x-ui.price amount="1234567890.99" compare-at="1999999999.99" currency="EUR" locale="de-DE" show-savings size="xl" :suffix="__('per seat, billed annually, taxes calculated at checkout')" />
</div>

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
amount string | int | float | null null The current price. A numeric value ("118", "118.00", 118) is formatted by currency and locale through money-value; any other string is shown verbatim.
compareAt string | int | float | null null The original price on a sale, rendered struck through with a visually-hidden "Original price" prefix. Same numeric-or-verbatim rule as `amount`.
currency string USD ISO 4217 code handed to money-value for numeric amounts.
locale string en-US BCP 47 locale that decides symbol side, grouping and decimal separators.
size sm | md | lg | xl md Scale of the current amount; the compare-at sits one step below it.
showSavings bool false Appends a "Save N%" badge computed from two numeric amounts when compareAt exceeds amount.
suffix string | null null Muted trailing text such as "/ month" or "each".

Slots

Default Blade slot only.

Data slots

Stable hooks for CSS overrides and browser tests.

price price-amount price-compare-at price-savings price-suffix

Behavior

  • Server-rendered Blade, no JavaScript.
  • Numeric amounts never touch floating-point maths: money-value formats them as strings.
  • Declares registry capability flags: a11y, authoredStateFixtures, responsive, rtl, darkMode, localized.

Guidance

Scannable data display

Present data for rapid scanning.

Use when

  • Use to summarize, sequence, or present data so users can scan it quickly.
  • Showing what a product costs on a card, a product page, a cart line or a sticky buy bar.
  • A sale needs the original price struck through beside the current one, read correctly by AT ("Sale price … Original price …").
  • You want the discount percentage derived rather than hard-coded: show-savings.

Avoid when

  • Do not add display-only ornament when the user needs actionable structure or exact comparison instead.
  • Presenting a single money figure that is not a price (a balance, a total, a fee) — compose <x-ui.money-value> directly.
  • Laying out plan tiers with a period and features — that is <x-ui.pricing-card>.

Use instead

  • Table for exact comparison
  • Plain text for a single value

Anti-patterns

  • Adding display ornament without informational value
Anatomy
price price-amount price-suffix price-compare-at price-savings
Theming hooks
price

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
No component-owned keyboard interaction; native element behavior applies.
Focus
none
  • Line-through is visual only; the compare-at amount carries a visually-hidden 'Original price' prefix and the current one 'Sale price' whenever both render.
  • The run is inline-flex with baseline alignment, so wrapping keeps the amounts readable.
  • Semantic HTML and a stable data-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, 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 under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Safe

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.

resources/views/components/ui/price.blade.php Blade
@props([
    'amount' => null,
    'compareAt' => null,
    'currency' => 'USD',
    'locale' => 'en-US',
    'size' => 'md',
    'showSavings' => false,
    'suffix' => null,
])

{{--
    Price: the current amount, an optional struck-through compare-at amount and
    an optional savings badge, in one baseline-aligned run.

    Each amount is a <x-ui.money-value>, so integer/decimal strings format by
    currency and locale without floating-point maths. A pre-formatted string
    ("$118.00", "From €9") is shown verbatim instead — handy for content that
    arrives already formatted, but the theme and locale cannot reach into it,
    so prefer numeric amounts.

    The visual line-through is not enough for AT: the compare-at amount is
    prefixed with a visually-hidden "Original price" and the current one with
    "Sale price" whenever both are present, so the sentence reads correctly.
--}}
@php
    $size = in_array($size, ['sm', 'md', 'lg', 'xl'], true) ? $size : 'md';

    // money-value's own scale carries the current amount; the compare-at sits
    // one step below it so the discount reads at a glance.
    $compareSize = ['sm' => 'sm', 'md' => 'sm', 'lg' => 'md', 'xl' => 'lg'][$size];
    $sizes = [
        'sm' => ['gap' => 'gap-1', 'text' => 'text-xs', 'compare' => 'text-xs', 'suffix' => 'text-xs'],
        'md' => ['gap' => 'gap-2', 'text' => 'text-sm font-semibold', 'compare' => 'text-sm', 'suffix' => 'text-xs'],
        'lg' => ['gap' => 'gap-2', 'text' => 'text-lg font-semibold', 'compare' => 'text-sm', 'suffix' => 'text-sm'],
        'xl' => ['gap' => 'gap-2', 'text' => 'text-2xl font-semibold tracking-tight', 'compare' => 'text-base', 'suffix' => 'text-sm'],
    ];
    $s = $sizes[$size];

    $isNumeric = static fn ($v): bool => $v !== null && preg_match('/^-?\d+(?:\.\d+)?$/', (string) $v) === 1;
    $amountIsNumeric = $isNumeric($amount);
    $compareIsNumeric = $isNumeric($compareAt);
    $hasCompare = $compareAt !== null && $compareAt !== '';

    // Savings can only be derived from two numeric amounts.
    $savings = null;
    if ($showSavings && $amountIsNumeric && $compareIsNumeric && (float) $compareAt > (float) $amount) {
        $savings = (int) round((1 - (float) $amount / (float) $compareAt) * 100);
    }
@endphp

<span
    data-slot="price"
    data-size="{{ $size }}"
    @if ($hasCompare) data-on-sale="true" @endif
    {{ $attributes->merge(['class' => 'inline-flex min-w-0 flex-wrap items-baseline '.$s['gap']]) }}
>
    @if ($hasCompare)
        <span class="sr-only">{{ __('Sale price') }}</span>
    @endif

    <span data-slot="price-amount" class="{{ 'whitespace-nowrap tabular-nums text-foreground '.$s['text'] }}">
        @if ($amountIsNumeric)
            <x-ui.money-value :amount="$amount" :currency="$currency" :locale="$locale" :size="$size" class="font-semibold" />
        @else
            {{ $amount }}
        @endif
    </span>

    @if ($suffix)
        <span data-slot="price-suffix" class="{{ 'text-muted-foreground '.$s['suffix'] }}">{{ $suffix }}</span>
    @endif

    @if ($hasCompare)
        <s data-slot="price-compare-at" class="{{ 'whitespace-nowrap text-muted-foreground '.$s['compare'] }}">
            <span class="sr-only">{{ __('Original price') }}</span>
            @if ($compareIsNumeric)
                <x-ui.money-value :amount="$compareAt" :currency="$currency" :locale="$locale" :size="$compareSize" class="!font-normal !text-muted-foreground" />
            @else
                {{ $compareAt }}
            @endif
        </s>
    @endif

    @if ($savings !== null)
        <span data-slot="price-savings" class="inline-flex self-center">
            <x-ui.badge variant="destructive" size="sm">{{ __('Save :percent%', ['percent' => $savings]) }}</x-ui.badge>
        </span>
    @endif
</span>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.0.1
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