Shopping Cart
Cart pages — a list of editable line items with quantity steppers beside a sticky order-summary card with a promo field and totals that recompute live.
Preview
@props([
'currency' => '$',
'taxRate' => 0.0825,
'shipping' => null,
'items' => null,
])
@php
$items ??= [
['name' => 'Aerolight Running Shoes', 'variant' => 'Slate / US 10', 'price' => 128.00, 'qty' => 1, 'badge' => 'New'],
['name' => 'Merino Crew Socks (3-pack)', 'variant' => 'Charcoal / One Size', 'price' => 27.00, 'qty' => 2, 'badge' => null],
['name' => 'Everyday Tech Backpack', 'variant' => 'Olive / 22 L', 'price' => 149.00, 'qty' => 1, 'badge' => 'Sale'],
['name' => 'Performance Dry-Fit Tee', 'variant' => 'Storm / M', 'price' => 48.00, 'qty' => 3, 'badge' => null],
];
$money = fn (float $n): string => $currency.number_format($n, 2);
$totalQty = array_reduce($items, fn ($c, $i) => $c + (int) $i['qty'], 0);
$subtotal = array_reduce($items, fn ($c, $i) => $c + (float) $i['price'] * (int) $i['qty'], 0.0);
$tax = round($subtotal * $taxRate, 2);
$shippingNum = is_numeric($shipping) ? (float) $shipping : ($subtotal >= 75 ? 0.0 : 9.99);
$shippingAmt = $shippingNum == 0.0 ? __('Free') : $money($shippingNum);
$orderTotal = $subtotal + $tax + $shippingNum;
$thumbTones = ['from-primary/20 to-accent/30', 'from-secondary/30 to-muted/40', 'from-accent/20 to-primary/10', 'from-muted/30 to-secondary/20'];
@endphp
<x-ui.section space="md" {{ $attributes }}>
<div class="mx-auto max-w-5xl px-4">
<header class="mb-8 flex flex-wrap items-baseline gap-x-4 gap-y-2">
<h1 class="text-2xl font-bold tracking-tight sm:text-3xl">{{ __('Shopping cart') }}</h1>
<x-ui.badge variant="secondary">{{ $totalQty }} {{ $totalQty === 1 ? __('item') : __('items') }}</x-ui.badge>
</header>
<div class="overflow-hidden rounded-lg border border-border bg-card">
<x-ui.table class="min-w-[640px]">
<caption class="sr-only">{{ __('Items in your cart') }}</caption>
<x-ui.table.header>
<x-ui.table.row class="hover:bg-transparent">
<x-ui.table.head scope="col">{{ __('Product') }}</x-ui.table.head>
<x-ui.table.head scope="col" align="end">{{ __('Price') }}</x-ui.table.head>
<x-ui.table.head scope="col" align="center">{{ __('Quantity') }}</x-ui.table.head>
<x-ui.table.head scope="col" align="end">{{ __('Total') }}</x-ui.table.head>
</x-ui.table.row>
</x-ui.table.header>
<x-ui.table.body>
@foreach ($items as $index => $item)
<x-ui.table.row>
<x-ui.table.cell>
<div class="flex items-center gap-4">
<div class="size-16 shrink-0 rounded-lg bg-gradient-to-br {{ $thumbTones[$index % count($thumbTones)] }}" aria-hidden="true"></div>
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-2">
<a href="#" class="text-sm font-semibold text-foreground hover:text-link">{{ __($item['name']) }}</a>
@if (! empty($item['badge']))<x-ui.badge variant="outline" size="sm">{{ __($item['badge']) }}</x-ui.badge>@endif
</div>
<p class="mt-0.5 text-sm text-muted-foreground">{{ __($item['variant']) }}</p>
<form method="POST" action="#" class="mt-2">
@csrf
@method('DELETE')
<x-ui.button type="submit" variant="ghost" size="sm" class="-ms-2 text-muted-foreground hover:text-destructive" aria-label="{{ __('Remove :name', ['name' => __($item['name'])]) }}">
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /></svg>
{{ __('Remove') }}
</x-ui.button>
</form>
</div>
</div>
</x-ui.table.cell>
<x-ui.table.cell align="end" class="tabular-nums text-foreground">{{ $money((float) $item['price']) }}</x-ui.table.cell>
<x-ui.table.cell align="center">
<x-ui.number-field
variant="stepper"
size="sm"
:value="$item['qty']"
:min="1"
:max="10"
:label="__('Quantity for :name', ['name' => __($item['name'])])"
/>
</x-ui.table.cell>
<x-ui.table.cell align="end" class="font-semibold tabular-nums text-foreground">{{ $money((float) $item['price'] * (int) $item['qty']) }}</x-ui.table.cell>
</x-ui.table.row>
@endforeach
</x-ui.table.body>
</x-ui.table>
</div>
<div class="mt-8 flex flex-col gap-6 sm:flex-row-reverse sm:items-start sm:gap-8">
<aside class="w-full rounded-lg border border-border bg-card p-6 sm:w-72 sm:shrink-0" aria-labelledby="cart-table-summary-title">
<h2 id="cart-table-summary-title" class="text-base font-semibold tracking-tight text-foreground">{{ __('Order summary') }}</h2>
<dl class="mt-6 space-y-4 text-sm">
<div class="flex items-center justify-between"><dt class="text-muted-foreground">{{ __('Subtotal') }}</dt><dd class="font-medium tabular-nums text-foreground">{{ $money($subtotal) }}</dd></div>
<div class="flex items-center justify-between"><dt class="text-muted-foreground">{{ __('Estimated shipping') }}</dt><dd class="font-medium text-foreground">{{ $shippingAmt }}</dd></div>
<div class="flex items-center justify-between"><dt class="text-muted-foreground">{{ __('Estimated tax') }}</dt><dd class="font-medium tabular-nums text-foreground">{{ $money($tax) }}</dd></div>
</dl>
<x-ui.separator class="my-6" />
<div class="flex items-center justify-between"><span class="text-base font-bold text-foreground">{{ __('Total') }}</span><span class="text-base font-bold tabular-nums text-foreground">{{ $money($orderTotal) }}</span></div>
</aside>
<div class="flex flex-1 flex-col gap-6">
<div>
<x-ui.label for="cart-table-promo" class="mb-2">{{ __('Promo code') }}</x-ui.label>
<div class="flex items-stretch gap-2">
<x-ui.input id="cart-table-promo" type="text" name="promo_code" autocomplete="off" placeholder="{{ __('Enter promo code') }}" class="flex-1" />
<x-ui.button type="button" variant="outline">{{ __('Apply') }}</x-ui.button>
</div>
</div>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center">
<x-ui.button type="button" class="w-full sm:flex-1" :with-arrow="true">{{ __('Proceed to checkout') }}</x-ui.button>
<a href="#" class="inline-flex w-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium text-muted-foreground transition-colors hover:text-foreground sm:w-auto">
<svg class="size-4 rtl:-scale-x-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 12H5M12 19l-7-7 7-7" /></svg>
{{ __('Continue shopping') }}
</a>
</div>
</div>
</div>
</div>
</x-ui.section>
Installation
php artisan ui:add blocks/shopping-cart-table
Registry contract
Install confidence
php artisan ui:add blocks/shopping-cart-table
writes only the generated targets below. The CLI validates each file hash before writing and
prompts before replacing local changes unless --force is used.
- Version
- 1.1.2
- License
- open
- Stability
- stable
- Contract
- v1
- Foundation
- ≥ 1.0.0
| Type | Generated target |
|---|---|
| blade | resources/views/blocks/shopping-cart-table.blade.php |
Registry dependencies
Package dependencies
composer: jml/brok:^0.2
Use with AI
A brief for your coding agent: what the block is, the install command, how to render it, its props and the rules. Copy it, or open a prompt about this block in an assistant.
# Brok UI block: Cart Table (`shopping-cart-table`)
A shopping cart in a table layout with product, price, quantity and total columns, a promo-code field and an order-summary block.
Brok UI is a Laravel Blade registry. `ui:add` copies this block into the app as plain Blade the app owns; it composes installed `<brok:*>` primitives and semantic design tokens.
## Install
```bash
php artisan ui:add blocks/shopping-cart-table
```
## Render it
```blade
<x-blocks.shopping-cart-table />
```
## Props
- `currency` (string, default `$`) — Currency symbol or code used to format prices.
- `tax-rate` (float, default `0.0825`) — Tax rate applied to the subtotal.
- `shipping` (mixed|null, default `null`) — Numeric shipping cost; null derives a sample free-shipping threshold.
- `items` (mixed|null, default `null`) — Cart line items with name, variant, price, quantity and badge; null falls back to sample data.
## Use when
- Displaying cart contents in a dense table layout with product, price, quantity and total columns.
- Wanting the cart to favour tabular scanning, such as in an admin or bulk-order context.
## Avoid when
- Wanting a card-based line-item layout instead of a table; use shopping-cart-two-column instead.
## Rules
- Render the installed block with `<x-blocks.shopping-cart-table />` and pass data through its props; edit the copied file only for structural changes.
- Keep the semantic design tokens (`bg-background`, `text-muted-foreground`); never swap in raw colour utilities.
- Keep the `data-slot` attributes and the logical (start/end) spacing so the markup still mirrors under `dir="rtl"`.
## Links
- Docs: https://brokui.dev/blocks/shopping-cart-table
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/shopping-cart-table.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Guidance
Use when
- Displaying cart contents in a dense table layout with product, price, quantity and total columns.
- Wanting the cart to favour tabular scanning, such as in an admin or bulk-order context.
Avoid when
- Wanting a card-based line-item layout instead of a table; use shopping-cart-two-column instead.
Anti-patterns
- Do not replace semantic props with conflicting utility classes.
- Do not remove labels, focus styles, or state attributes.
Usage
Render the block as a component, passing data where useful:
<x-blocks.shopping-cart-table />
Built from primitives
This block composes installed <brok:*> primitives and semantic design
tokens only — it does not reimplement any primitive. Re-theme it (light, dark, admin, customer) by
editing CSS variables; flip the preview to RTL to confirm it mirrors.
Source
The exact, editable file ui:add writes
into your app. The preview above renders this same source — there are no preview-only blocks.
@props([
'currency' => '$',
'taxRate' => 0.0825,
'shipping' => null,
'items' => null,
])
@php
$items ??= [
['name' => 'Aerolight Running Shoes', 'variant' => 'Slate / US 10', 'price' => 128.00, 'qty' => 1, 'badge' => 'New'],
['name' => 'Merino Crew Socks (3-pack)', 'variant' => 'Charcoal / One Size', 'price' => 27.00, 'qty' => 2, 'badge' => null],
['name' => 'Everyday Tech Backpack', 'variant' => 'Olive / 22 L', 'price' => 149.00, 'qty' => 1, 'badge' => 'Sale'],
['name' => 'Performance Dry-Fit Tee', 'variant' => 'Storm / M', 'price' => 48.00, 'qty' => 3, 'badge' => null],
];
$money = fn (float $n): string => $currency.number_format($n, 2);
$totalQty = array_reduce($items, fn ($c, $i) => $c + (int) $i['qty'], 0);
$subtotal = array_reduce($items, fn ($c, $i) => $c + (float) $i['price'] * (int) $i['qty'], 0.0);
$tax = round($subtotal * $taxRate, 2);
$shippingNum = is_numeric($shipping) ? (float) $shipping : ($subtotal >= 75 ? 0.0 : 9.99);
$shippingAmt = $shippingNum == 0.0 ? __('Free') : $money($shippingNum);
$orderTotal = $subtotal + $tax + $shippingNum;
$thumbTones = ['from-primary/20 to-accent/30', 'from-secondary/30 to-muted/40', 'from-accent/20 to-primary/10', 'from-muted/30 to-secondary/20'];
@endphp
<x-ui.section space="md" {{ $attributes }}>
<div class="mx-auto max-w-5xl px-4">
<header class="mb-8 flex flex-wrap items-baseline gap-x-4 gap-y-2">
<h1 class="text-2xl font-bold tracking-tight sm:text-3xl">{{ __('Shopping cart') }}</h1>
<x-ui.badge variant="secondary">{{ $totalQty }} {{ $totalQty === 1 ? __('item') : __('items') }}</x-ui.badge>
</header>
<div class="overflow-hidden rounded-lg border border-border bg-card">
<x-ui.table class="min-w-[640px]">
<caption class="sr-only">{{ __('Items in your cart') }}</caption>
<x-ui.table.header>
<x-ui.table.row class="hover:bg-transparent">
<x-ui.table.head scope="col">{{ __('Product') }}</x-ui.table.head>
<x-ui.table.head scope="col" align="end">{{ __('Price') }}</x-ui.table.head>
<x-ui.table.head scope="col" align="center">{{ __('Quantity') }}</x-ui.table.head>
<x-ui.table.head scope="col" align="end">{{ __('Total') }}</x-ui.table.head>
</x-ui.table.row>
</x-ui.table.header>
<x-ui.table.body>
@foreach ($items as $index => $item)
<x-ui.table.row>
<x-ui.table.cell>
<div class="flex items-center gap-4">
<div class="size-16 shrink-0 rounded-lg bg-gradient-to-br {{ $thumbTones[$index % count($thumbTones)] }}" aria-hidden="true"></div>
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-2">
<a href="#" class="text-sm font-semibold text-foreground hover:text-link">{{ __($item['name']) }}</a>
@if (! empty($item['badge']))<x-ui.badge variant="outline" size="sm">{{ __($item['badge']) }}</x-ui.badge>@endif
</div>
<p class="mt-0.5 text-sm text-muted-foreground">{{ __($item['variant']) }}</p>
<form method="POST" action="#" class="mt-2">
@csrf
@method('DELETE')
<x-ui.button type="submit" variant="ghost" size="sm" class="-ms-2 text-muted-foreground hover:text-destructive" aria-label="{{ __('Remove :name', ['name' => __($item['name'])]) }}">
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /></svg>
{{ __('Remove') }}
</x-ui.button>
</form>
</div>
</div>
</x-ui.table.cell>
<x-ui.table.cell align="end" class="tabular-nums text-foreground">{{ $money((float) $item['price']) }}</x-ui.table.cell>
<x-ui.table.cell align="center">
<x-ui.number-field
variant="stepper"
size="sm"
:value="$item['qty']"
:min="1"
:max="10"
:label="__('Quantity for :name', ['name' => __($item['name'])])"
/>
</x-ui.table.cell>
<x-ui.table.cell align="end" class="font-semibold tabular-nums text-foreground">{{ $money((float) $item['price'] * (int) $item['qty']) }}</x-ui.table.cell>
</x-ui.table.row>
@endforeach
</x-ui.table.body>
</x-ui.table>
</div>
<div class="mt-8 flex flex-col gap-6 sm:flex-row-reverse sm:items-start sm:gap-8">
<aside class="w-full rounded-lg border border-border bg-card p-6 sm:w-72 sm:shrink-0" aria-labelledby="cart-table-summary-title">
<h2 id="cart-table-summary-title" class="text-base font-semibold tracking-tight text-foreground">{{ __('Order summary') }}</h2>
<dl class="mt-6 space-y-4 text-sm">
<div class="flex items-center justify-between"><dt class="text-muted-foreground">{{ __('Subtotal') }}</dt><dd class="font-medium tabular-nums text-foreground">{{ $money($subtotal) }}</dd></div>
<div class="flex items-center justify-between"><dt class="text-muted-foreground">{{ __('Estimated shipping') }}</dt><dd class="font-medium text-foreground">{{ $shippingAmt }}</dd></div>
<div class="flex items-center justify-between"><dt class="text-muted-foreground">{{ __('Estimated tax') }}</dt><dd class="font-medium tabular-nums text-foreground">{{ $money($tax) }}</dd></div>
</dl>
<x-ui.separator class="my-6" />
<div class="flex items-center justify-between"><span class="text-base font-bold text-foreground">{{ __('Total') }}</span><span class="text-base font-bold tabular-nums text-foreground">{{ $money($orderTotal) }}</span></div>
</aside>
<div class="flex flex-1 flex-col gap-6">
<div>
<x-ui.label for="cart-table-promo" class="mb-2">{{ __('Promo code') }}</x-ui.label>
<div class="flex items-stretch gap-2">
<x-ui.input id="cart-table-promo" type="text" name="promo_code" autocomplete="off" placeholder="{{ __('Enter promo code') }}" class="flex-1" />
<x-ui.button type="button" variant="outline">{{ __('Apply') }}</x-ui.button>
</div>
</div>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center">
<x-ui.button type="button" class="w-full sm:flex-1" :with-arrow="true">{{ __('Proceed to checkout') }}</x-ui.button>
<a href="#" class="inline-flex w-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium text-muted-foreground transition-colors hover:text-foreground sm:w-auto">
<svg class="size-4 rtl:-scale-x-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 12H5M12 19l-7-7 7-7" /></svg>
{{ __('Continue shopping') }}
</a>
</div>
</div>
</div>
</div>
</x-ui.section>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.1.2 - Status
- Stable
- License
-
open - Deprecation
- Not deprecated
- Contract
-
v1 - Foundation
-
≥ 1.0.0