Skip to content
Brok UI

Loading…

No results

Shopping Cart

Open source

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

resources/views/blocks/shopping-cart-table.blade.php Blade
@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

terminal
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

badge button input label number-field section separator table

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.

shopping-cart-table.md
# 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:

resources/views/welcome.blade.php Blade
<x-blocks.shopping-cart-table />

Built from primitives

Composes <brok:*> Semantic tokens Re-themeable RTL-ready

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.

resources/views/blocks/shopping-cart-table.blade.php Blade
@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