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([
'eyebrow' => 'Shopping bag',
'heading' => 'Your selection',
'currency' => '$',
'shippingNote' => 'Shipping & taxes calculated at checkout',
'items' => null,
])
@php
$items ??= [
[
'name' => 'Structured Leather Tote',
'variant' => 'Cognac / One size',
'price' => 480.0,
'qty' => 1,
],
[
'name' => 'Monogram Silk Scarf',
'variant' => 'Ivory / 90cm',
'price' => 165.0,
'qty' => 2,
],
[
'name' => 'Calfskin Card Holder',
'variant' => 'Black',
'price' => 95.0,
'qty' => 1,
],
];
@endphp
{{--
shopping-cart-04 — "Editorial" Monogram cart page. A distinct sibling of
the other shopping-cart compositions: instead of bordered cards it lays line items out as an
airy, separator-divided list with uppercase wide-tracked labels and a quiet
`font-heading` headline, beside a borderless order-summary column carrying an
optional order-note textarea, a large total, a full-width checkout button and a
continue-shopping link. Inline Alpine recomputes the subtotal/total live as
quantities change. Composes button, separator, textarea, label, aspect-ratio, number-field.
Semantic tokens only, logical spacing, WCAG AA, RTL-safe.
--}}
<section
{{ $attributes->merge(['class' => 'bg-background py-14 text-foreground sm:py-20']) }}
data-slot="shopping-cart"
>
<div
class="mx-auto max-w-6xl px-6 sm:px-8"
x-data="{
currency: @js($currency),
cart: @js(collect($items)->map(fn ($i) => [
'name' => __($i['name']),
'variant' => __($i['variant']),
'price' => (float) $i['price'],
'qty' => max(1, min(10, (int) $i['qty'])),
])->values()),
remove(i) { this.cart.splice(i, 1) },
money(n) { return this.currency + n.toFixed(2) },
get subtotal() { return this.cart.reduce((sum, item) => sum + item.price * item.qty, 0) },
get total() { return this.subtotal },
get count() { return this.cart.reduce((sum, item) => sum + item.qty, 0) },
}"
>
{{-- ── Editorial header ── --}}
<header class="border-b border-border pb-8">
<p class="font-body text-sm uppercase tracking-[0.3em] text-muted-foreground">
{{ __($eyebrow) }}
</p>
<h1 class="mt-4 font-heading text-4xl font-normal tracking-tight sm:text-5xl">
{{ __($heading) }}
</h1>
<p class="mt-4 font-body text-sm text-muted-foreground">
<span x-text="count"></span>
<span x-text="count === 1 ? @js(__('item in your bag')) : @js(__('items in your bag'))"></span>
</p>
</header>
<div class="grid grid-cols-1 gap-x-16 gap-y-12 lg:grid-cols-3 lg:items-start">
{{-- ── Left: line items ── --}}
<div class="lg:col-span-2">
<template x-if="cart.length === 0">
<div class="flex flex-col items-start gap-4 py-16">
<p class="font-heading text-2xl font-normal text-foreground">{{ __('Your bag is empty') }}</p>
<x-ui.button href="#" variant="outline">{{ __('Continue shopping') }}</x-ui.button>
</div>
</template>
<ul role="list" x-show="cart.length > 0" class="divide-y divide-border">
<template x-for="(item, i) in cart" :key="item.name + i">
<li class="flex items-start gap-6 py-8 first:pt-10">
{{-- Media placeholder --}}
<div class="w-24 shrink-0 sm:w-28">
<x-ui.aspect-ratio ratio="3 / 4" class="overflow-hidden bg-muted">
<svg
class="absolute inset-0 size-full text-muted-foreground/30"
viewBox="0 0 100 100" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
preserveAspectRatio="xMidYMid slice" aria-hidden="true"
>
<rect x="24" y="28" width="52" height="44" rx="2" />
<path d="m24 60 14-12 12 10 10-8 16 12" />
<circle cx="60" cy="42" r="4" />
</svg>
</x-ui.aspect-ratio>
</div>
{{-- Details --}}
<div class="flex min-w-0 flex-1 flex-col gap-4">
<div class="flex items-start justify-between gap-6">
<div class="min-w-0">
<h2 class="truncate font-heading text-sm font-normal text-foreground">
<a href="#" class="transition-colors hover:text-link focus-visible:text-link focus-visible:outline-none" x-text="item.name"></a>
</h2>
<p class="mt-2 font-body text-sm uppercase tracking-[0.2em] text-muted-foreground" x-text="item.variant"></p>
<p class="mt-2 font-body text-sm text-muted-foreground" x-text="money(item.price)"></p>
</div>
<p class="shrink-0 font-body text-sm text-foreground" x-text="money(item.price * item.qty)"></p>
</div>
<div class="flex flex-wrap items-center justify-between gap-2 sm:gap-6">
{{-- Quantity stepper: the number-field root is x-modelable, so the
x-for row's `item.qty` drives it and the totals stay live. --}}
<x-ui.number-field
variant="stepper"
size="sm"
x-model="item.qty"
:min="1"
:max="10"
::aria-label="{{ \Illuminate\Support\Js::from(__('Quantity for')) }} + ' ' + item.name"
/>
<button
type="button"
class="font-body text-sm uppercase tracking-[0.2em] text-muted-foreground underline-offset-4 transition-colors hover:text-destructive hover:underline focus-visible:text-destructive focus-visible:underline focus-visible:outline-none"
x-on:click="remove(i)"
>
{{ __('Remove') }}
</button>
</div>
</div>
</li>
</template>
</ul>
</div>
{{-- ── Right: order summary ── --}}
<aside class="lg:sticky lg:top-10" aria-labelledby="cart-summary-title">
<h2 id="cart-summary-title" class="font-body text-sm uppercase tracking-[0.3em] text-muted-foreground">
{{ __('Order summary') }}
</h2>
<dl class="mt-6 space-y-4 font-body text-sm">
<div class="flex items-center justify-between">
<dt class="text-muted-foreground">{{ __('Subtotal') }}</dt>
<dd class="text-foreground" x-text="money(subtotal)"></dd>
</div>
<div class="flex items-center justify-between">
<dt class="text-muted-foreground">{{ __('Shipping') }}</dt>
<dd class="text-foreground">{{ __('Calculated at checkout') }}</dd>
</div>
</dl>
<x-ui.separator class="my-8" />
{{-- Optional order note --}}
<div class="space-y-2">
<x-ui.label for="cart-note" class="font-body text-sm uppercase tracking-[0.2em] text-muted-foreground">
{{ __('Order note') }}
</x-ui.label>
<x-ui.textarea
id="cart-note"
name="order_note"
rows="3"
placeholder="{{ __('Add a gift message or delivery instructions') }}"
/>
</div>
<x-ui.separator class="my-8" />
<div class="flex items-baseline justify-between">
<span class="font-body text-sm uppercase tracking-[0.2em] text-muted-foreground">{{ __('Total') }}</span>
<span class="font-heading text-2xl font-normal text-foreground" x-text="money(total)"></span>
</div>
<p class="mt-2 font-body text-sm text-muted-foreground">{{ __($shippingNote) }}</p>
<x-ui.button type="button" size="lg" class="mt-8 w-full" x-bind:disabled="cart.length === 0">
{{ __('Proceed to checkout') }}
</x-ui.button>
<p class="mt-6 text-center">
<a href="#" class="font-body text-sm uppercase tracking-[0.2em] text-muted-foreground underline-offset-4 transition-colors hover:text-foreground hover:underline focus-visible:text-foreground focus-visible:underline focus-visible:outline-none">
{{ __('Continue shopping') }}
</a>
</p>
</aside>
</div>
</div>
</section>
Installation
php artisan ui:add blocks/shopping-cart-editorial
Registry contract
Install confidence
php artisan ui:add blocks/shopping-cart-editorial
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.1
- License
- open
- Stability
- stable
- Contract
- v1
- Foundation
- ≥ 1.0.0
| Type | Generated target |
|---|---|
| blade | resources/views/blocks/shopping-cart-editorial.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: Shopping Cart Editorial (`shopping-cart-editorial`)
An editorial Monogram cart page that lays line items out as an airy separator-divided list with uppercase wide-tracked labels, a media placeholder, variant, unit price, a number-field stepper clamped 1–10, live line total and remove link, beside a borderless order-summary column with subtotal, a shipping note, an optional order-note textarea, a large total, a full-width checkout button and a continue-shopping link — totals recompute live as quantities change.
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-editorial
```
## Render it
```blade
<x-blocks.shopping-cart-editorial />
```
## Props
- `eyebrow` (string, default `Shopping bag`) — Small label shown above the heading.
- `heading` (string, default `Your selection`) — Section heading.
- `currency` (string, default `$`) — Currency symbol or code used to format prices.
- `shipping-note` (string, default `Shipping & taxes calculated at checkout`) — Note shown about shipping and tax calculation.
- `items` (mixed|null, default `null`) — Cart line items with name, variant, price and quantity; null falls back to sample data.
## Use when
- Building an editorial, borderless cart page for a premium or fashion storefront with airy separator-divided line items.
- Wanting quiet, uppercase-tracked typography over bordered cards or a table.
## Avoid when
- Wanting a bordered card or table layout instead of an editorial one; use shopping-cart-two-column or shopping-cart-table instead.
## Rules
- Render the installed block with `<x-blocks.shopping-cart-editorial />` 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-editorial
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/shopping-cart-editorial.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
- Building an editorial, borderless cart page for a premium or fashion storefront with airy separator-divided line items.
- Wanting quiet, uppercase-tracked typography over bordered cards or a table.
Avoid when
- Wanting a bordered card or table layout instead of an editorial one; use shopping-cart-two-column or shopping-cart-table instead.
Anti-patterns
- Do not replace semantic props with conflicting utility classes.
- Do not remove labels, focus styles, or state attributes.
- Anatomy
Usage
Render the block as a component, passing data where useful:
<x-blocks.shopping-cart-editorial />
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([
'eyebrow' => 'Shopping bag',
'heading' => 'Your selection',
'currency' => '$',
'shippingNote' => 'Shipping & taxes calculated at checkout',
'items' => null,
])
@php
$items ??= [
[
'name' => 'Structured Leather Tote',
'variant' => 'Cognac / One size',
'price' => 480.0,
'qty' => 1,
],
[
'name' => 'Monogram Silk Scarf',
'variant' => 'Ivory / 90cm',
'price' => 165.0,
'qty' => 2,
],
[
'name' => 'Calfskin Card Holder',
'variant' => 'Black',
'price' => 95.0,
'qty' => 1,
],
];
@endphp
{{--
shopping-cart-04 — "Editorial" Monogram cart page. A distinct sibling of
the other shopping-cart compositions: instead of bordered cards it lays line items out as an
airy, separator-divided list with uppercase wide-tracked labels and a quiet
`font-heading` headline, beside a borderless order-summary column carrying an
optional order-note textarea, a large total, a full-width checkout button and a
continue-shopping link. Inline Alpine recomputes the subtotal/total live as
quantities change. Composes button, separator, textarea, label, aspect-ratio, number-field.
Semantic tokens only, logical spacing, WCAG AA, RTL-safe.
--}}
<section
{{ $attributes->merge(['class' => 'bg-background py-14 text-foreground sm:py-20']) }}
data-slot="shopping-cart"
>
<div
class="mx-auto max-w-6xl px-6 sm:px-8"
x-data="{
currency: @js($currency),
cart: @js(collect($items)->map(fn ($i) => [
'name' => __($i['name']),
'variant' => __($i['variant']),
'price' => (float) $i['price'],
'qty' => max(1, min(10, (int) $i['qty'])),
])->values()),
remove(i) { this.cart.splice(i, 1) },
money(n) { return this.currency + n.toFixed(2) },
get subtotal() { return this.cart.reduce((sum, item) => sum + item.price * item.qty, 0) },
get total() { return this.subtotal },
get count() { return this.cart.reduce((sum, item) => sum + item.qty, 0) },
}"
>
{{-- ── Editorial header ── --}}
<header class="border-b border-border pb-8">
<p class="font-body text-sm uppercase tracking-[0.3em] text-muted-foreground">
{{ __($eyebrow) }}
</p>
<h1 class="mt-4 font-heading text-4xl font-normal tracking-tight sm:text-5xl">
{{ __($heading) }}
</h1>
<p class="mt-4 font-body text-sm text-muted-foreground">
<span x-text="count"></span>
<span x-text="count === 1 ? @js(__('item in your bag')) : @js(__('items in your bag'))"></span>
</p>
</header>
<div class="grid grid-cols-1 gap-x-16 gap-y-12 lg:grid-cols-3 lg:items-start">
{{-- ── Left: line items ── --}}
<div class="lg:col-span-2">
<template x-if="cart.length === 0">
<div class="flex flex-col items-start gap-4 py-16">
<p class="font-heading text-2xl font-normal text-foreground">{{ __('Your bag is empty') }}</p>
<x-ui.button href="#" variant="outline">{{ __('Continue shopping') }}</x-ui.button>
</div>
</template>
<ul role="list" x-show="cart.length > 0" class="divide-y divide-border">
<template x-for="(item, i) in cart" :key="item.name + i">
<li class="flex items-start gap-6 py-8 first:pt-10">
{{-- Media placeholder --}}
<div class="w-24 shrink-0 sm:w-28">
<x-ui.aspect-ratio ratio="3 / 4" class="overflow-hidden bg-muted">
<svg
class="absolute inset-0 size-full text-muted-foreground/30"
viewBox="0 0 100 100" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
preserveAspectRatio="xMidYMid slice" aria-hidden="true"
>
<rect x="24" y="28" width="52" height="44" rx="2" />
<path d="m24 60 14-12 12 10 10-8 16 12" />
<circle cx="60" cy="42" r="4" />
</svg>
</x-ui.aspect-ratio>
</div>
{{-- Details --}}
<div class="flex min-w-0 flex-1 flex-col gap-4">
<div class="flex items-start justify-between gap-6">
<div class="min-w-0">
<h2 class="truncate font-heading text-sm font-normal text-foreground">
<a href="#" class="transition-colors hover:text-link focus-visible:text-link focus-visible:outline-none" x-text="item.name"></a>
</h2>
<p class="mt-2 font-body text-sm uppercase tracking-[0.2em] text-muted-foreground" x-text="item.variant"></p>
<p class="mt-2 font-body text-sm text-muted-foreground" x-text="money(item.price)"></p>
</div>
<p class="shrink-0 font-body text-sm text-foreground" x-text="money(item.price * item.qty)"></p>
</div>
<div class="flex flex-wrap items-center justify-between gap-2 sm:gap-6">
{{-- Quantity stepper: the number-field root is x-modelable, so the
x-for row's `item.qty` drives it and the totals stay live. --}}
<x-ui.number-field
variant="stepper"
size="sm"
x-model="item.qty"
:min="1"
:max="10"
::aria-label="{{ \Illuminate\Support\Js::from(__('Quantity for')) }} + ' ' + item.name"
/>
<button
type="button"
class="font-body text-sm uppercase tracking-[0.2em] text-muted-foreground underline-offset-4 transition-colors hover:text-destructive hover:underline focus-visible:text-destructive focus-visible:underline focus-visible:outline-none"
x-on:click="remove(i)"
>
{{ __('Remove') }}
</button>
</div>
</div>
</li>
</template>
</ul>
</div>
{{-- ── Right: order summary ── --}}
<aside class="lg:sticky lg:top-10" aria-labelledby="cart-summary-title">
<h2 id="cart-summary-title" class="font-body text-sm uppercase tracking-[0.3em] text-muted-foreground">
{{ __('Order summary') }}
</h2>
<dl class="mt-6 space-y-4 font-body text-sm">
<div class="flex items-center justify-between">
<dt class="text-muted-foreground">{{ __('Subtotal') }}</dt>
<dd class="text-foreground" x-text="money(subtotal)"></dd>
</div>
<div class="flex items-center justify-between">
<dt class="text-muted-foreground">{{ __('Shipping') }}</dt>
<dd class="text-foreground">{{ __('Calculated at checkout') }}</dd>
</div>
</dl>
<x-ui.separator class="my-8" />
{{-- Optional order note --}}
<div class="space-y-2">
<x-ui.label for="cart-note" class="font-body text-sm uppercase tracking-[0.2em] text-muted-foreground">
{{ __('Order note') }}
</x-ui.label>
<x-ui.textarea
id="cart-note"
name="order_note"
rows="3"
placeholder="{{ __('Add a gift message or delivery instructions') }}"
/>
</div>
<x-ui.separator class="my-8" />
<div class="flex items-baseline justify-between">
<span class="font-body text-sm uppercase tracking-[0.2em] text-muted-foreground">{{ __('Total') }}</span>
<span class="font-heading text-2xl font-normal text-foreground" x-text="money(total)"></span>
</div>
<p class="mt-2 font-body text-sm text-muted-foreground">{{ __($shippingNote) }}</p>
<x-ui.button type="button" size="lg" class="mt-8 w-full" x-bind:disabled="cart.length === 0">
{{ __('Proceed to checkout') }}
</x-ui.button>
<p class="mt-6 text-center">
<a href="#" class="font-body text-sm uppercase tracking-[0.2em] text-muted-foreground underline-offset-4 transition-colors hover:text-foreground hover:underline focus-visible:text-foreground focus-visible:underline focus-visible:outline-none">
{{ __('Continue shopping') }}
</a>
</p>
</aside>
</div>
</div>
</section>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.1.1 - Status
- Stable
- License
-
open - Deprecation
- Not deprecated
- Contract
-
v1 - Foundation
-
≥ 1.0.0