Featured Product
Single-product spotlights — a hero product with media, variant swatches and add-to-cart.
Preview
@props([
'sectionTitle' => 'Featured Product',
'sectionSubcopy' => 'Carefully selected to represent the best of our latest collection — practical, durable, and built to be used daily.',
'brand' => 'Monogram',
'eyebrow' => 'Editor’s Pick',
'name' => 'The Carry Weekender',
'price' => '$320.00',
'comparePrice' => null,
'description' => 'A structured full-grain leather holdall, hand-finished and built to soften beautifully with every journey. One considered piece for the weekends that matter.',
'colors' => null,
'primaryCta' => ['label' => 'Add to cart', 'href' => '#'],
'secondaryCta' => ['label' => 'View details', 'href' => '#'],
])
{{--
featured-product-01 — "Featured Product" editorial spotlight (Monogram theme).
Structurally distinct from product-overview-detail (a full PDP with gallery,
rating, sizes, quantity stepper, feature grid): this is a single homepage
spotlight — an optional section heading above a two-column split, a large
`bg-muted` media placeholder on one side and, on the other, a brand/eyebrow,
a large `font-heading` product name, price, short description, selectable
Alpine-bound colour swatches, and dual add-to-cart / secondary CTAs.
Composes <x-ui.button>, <x-ui.badge> and <x-ui.color-swatch>.
Semantic tokens only, logical spacing, WCAG AA, RTL-safe.
--}}
@php
// Swatches reference semantic tokens by class name (never raw hex), so a
// re-theme recolours the dots automatically and the raw-colour guard passes.
$colors ??= [
['name' => 'Charcoal', 'class' => 'bg-foreground'],
['name' => 'Stone', 'class' => 'bg-muted-foreground'],
['name' => 'Sand', 'class' => 'bg-secondary'],
['name' => 'Clay', 'class' => 'bg-accent'],
];
// <x-ui.color-swatch> takes value/label/token entries; the Alpine `color`
// state holds the option value and the legend reads its label from a map.
$swatches = collect($colors)->map(fn ($color) => [
'value' => $color['value'] ?? $color['name'],
'label' => __($color['name']),
'token' => $color['class'],
])->values()->all();
$colorLabels = collect($swatches)->pluck('label', 'value')->all();
@endphp
<section
{{ $attributes->merge(['class' => 'bg-background py-16 text-foreground sm:py-20 lg:py-24']) }}
data-slot="featured-product"
aria-label="{{ __($sectionTitle) }}"
>
<div class="mx-auto max-w-7xl px-6 sm:px-8 lg:px-12">
{{-- ── Section heading ── --}}
@if ($sectionTitle || $sectionSubcopy)
<div class="mb-10 flex max-w-2xl flex-col gap-4 sm:mb-14">
@if ($sectionTitle)
<h2 class="font-heading text-3xl font-normal tracking-tight sm:text-4xl">{{ __($sectionTitle) }}</h2>
@endif
@if ($sectionSubcopy)
<p class="font-body text-base leading-relaxed text-muted-foreground">{{ __($sectionSubcopy) }}</p>
@endif
</div>
@endif
<div
x-data="{ color: @js($swatches[0]['value'] ?? null), colors: @js($colorLabels) }"
class="grid grid-cols-1 items-center gap-10 lg:grid-cols-2 lg:gap-16"
>
{{-- ── Media placeholder ── --}}
<div class="relative flex aspect-[4/5] w-full items-center justify-center overflow-hidden rounded-lg bg-muted sm:aspect-square lg:aspect-[4/5]" role="img" aria-label="{{ __($name) }}">
@if ($eyebrow)
<span class="absolute start-5 top-5">
<x-ui.badge variant="secondary" class="font-body text-base uppercase tracking-widest">{{ __($eyebrow) }}</x-ui.badge>
</span>
@endif
<svg class="size-28 text-muted-foreground/40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M3 7l9-4 9 4-9 4-9-4Z" />
<path d="M3 7v10l9 4 9-4V7" />
<path d="M12 11v10" />
</svg>
</div>
{{-- ── Details ── --}}
<div class="flex flex-col items-start gap-6">
@if ($brand)
<p class="font-body text-base uppercase tracking-[0.3em] text-muted-foreground">{{ __($brand) }}</p>
@endif
<h3 class="font-heading text-balance text-3xl font-normal leading-[1.05] tracking-tight sm:text-4xl">{{ __($name) }}</h3>
<div class="flex items-baseline gap-4">
<span class="text-2xl font-semibold tracking-tight">{{ $price }}</span>
@if ($comparePrice)
<span class="text-base text-muted-foreground line-through">{{ $comparePrice }}</span>
@endif
</div>
<p class="max-w-md text-pretty font-body text-base leading-relaxed text-muted-foreground">{{ __($description) }}</p>
{{-- Colour swatches --}}
<fieldset class="flex flex-col gap-4">
<legend class="font-body text-base font-medium text-foreground">
{{ __('Colour') }}:
<span class="text-muted-foreground" x-text="colors[color]"></span>
</legend>
<x-ui.color-swatch name="color" model="color" :selected="$swatches[0]['value'] ?? null" :colors="$swatches" />
</fieldset>
{{-- Actions --}}
<div class="mt-2 flex w-full flex-col gap-4 sm:flex-row">
<x-ui.button :href="$primaryCta['href']" size="lg" class="sm:flex-1">
<x-slot:icon>
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="9" cy="21" r="1" />
<circle cx="20" cy="21" r="1" />
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6" />
</svg>
</x-slot:icon>
{{ __($primaryCta['label']) }}
</x-ui.button>
@if ($secondaryCta)
<x-ui.button :href="$secondaryCta['href']" variant="outline" size="lg" :withArrow="true">
{{ __($secondaryCta['label']) }}
</x-ui.button>
@endif
</div>
</div>
</div>
</div>
</section>
Installation
php artisan ui:add blocks/featured-product-spotlight
Registry contract
Install confidence
php artisan ui:add blocks/featured-product-spotlight
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/featured-product-spotlight.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: Featured Product (`featured-product-spotlight`)
An editorial featured-product spotlight — a single hero product presented with a large media placeholder beside a brand eyebrow, large display name, price, short description, selectable colour swatches and dual add-to-cart / secondary CTAs.
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/featured-product-spotlight
```
## Render it
```blade
<x-blocks.featured-product-spotlight />
```
## Props
- `section-title` (string, default `Featured Product`)
- `section-subcopy` (string, default `Carefully selected to represent the best of our latest collection — practical, durable, and built to be used daily.`)
- `brand` (string, default `Monogram`)
- `eyebrow` (string, default `Editor’s Pick`)
- `name` (string, default `The Carry Weekender`)
- `price` (string, default `$320.00`)
- `compare-price` (mixed|null, default `null`)
- `description` (string, default `A structured full-grain leather holdall, hand-finished and built to soften beautifully with every journey. One considered piece for the weekends that matter.`)
- `colors` (mixed|null, default `null`)
- `primary-cta` (array, default `['label' => 'Add to cart', 'href' => '#']`)
- `secondary-cta` (array, default `['label' => 'View details', 'href' => '#']`)
## Use when
- Spotlighting a single hero product with a large media placeholder, brand eyebrow, price and short description.
- Letting a shopper pick a colour via swatches before adding to cart with dual primary/secondary CTAs.
## Avoid when
- Use compare-products for comparing several products, or bundle-builder-box for a multi-item build-your-own bundle.
## Rules
- Render the installed block with `<x-blocks.featured-product-spotlight />` 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/featured-product-spotlight
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/featured-product-spotlight.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
- Spotlighting a single hero product with a large media placeholder, brand eyebrow, price and short description.
- Letting a shopper pick a colour via swatches before adding to cart with dual primary/secondary CTAs.
Avoid when
- Use compare-products for comparing several products, or bundle-builder-box for a multi-item build-your-own bundle.
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.featured-product-spotlight />
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([
'sectionTitle' => 'Featured Product',
'sectionSubcopy' => 'Carefully selected to represent the best of our latest collection — practical, durable, and built to be used daily.',
'brand' => 'Monogram',
'eyebrow' => 'Editor’s Pick',
'name' => 'The Carry Weekender',
'price' => '$320.00',
'comparePrice' => null,
'description' => 'A structured full-grain leather holdall, hand-finished and built to soften beautifully with every journey. One considered piece for the weekends that matter.',
'colors' => null,
'primaryCta' => ['label' => 'Add to cart', 'href' => '#'],
'secondaryCta' => ['label' => 'View details', 'href' => '#'],
])
{{--
featured-product-01 — "Featured Product" editorial spotlight (Monogram theme).
Structurally distinct from product-overview-detail (a full PDP with gallery,
rating, sizes, quantity stepper, feature grid): this is a single homepage
spotlight — an optional section heading above a two-column split, a large
`bg-muted` media placeholder on one side and, on the other, a brand/eyebrow,
a large `font-heading` product name, price, short description, selectable
Alpine-bound colour swatches, and dual add-to-cart / secondary CTAs.
Composes <x-ui.button>, <x-ui.badge> and <x-ui.color-swatch>.
Semantic tokens only, logical spacing, WCAG AA, RTL-safe.
--}}
@php
// Swatches reference semantic tokens by class name (never raw hex), so a
// re-theme recolours the dots automatically and the raw-colour guard passes.
$colors ??= [
['name' => 'Charcoal', 'class' => 'bg-foreground'],
['name' => 'Stone', 'class' => 'bg-muted-foreground'],
['name' => 'Sand', 'class' => 'bg-secondary'],
['name' => 'Clay', 'class' => 'bg-accent'],
];
// <x-ui.color-swatch> takes value/label/token entries; the Alpine `color`
// state holds the option value and the legend reads its label from a map.
$swatches = collect($colors)->map(fn ($color) => [
'value' => $color['value'] ?? $color['name'],
'label' => __($color['name']),
'token' => $color['class'],
])->values()->all();
$colorLabels = collect($swatches)->pluck('label', 'value')->all();
@endphp
<section
{{ $attributes->merge(['class' => 'bg-background py-16 text-foreground sm:py-20 lg:py-24']) }}
data-slot="featured-product"
aria-label="{{ __($sectionTitle) }}"
>
<div class="mx-auto max-w-7xl px-6 sm:px-8 lg:px-12">
{{-- ── Section heading ── --}}
@if ($sectionTitle || $sectionSubcopy)
<div class="mb-10 flex max-w-2xl flex-col gap-4 sm:mb-14">
@if ($sectionTitle)
<h2 class="font-heading text-3xl font-normal tracking-tight sm:text-4xl">{{ __($sectionTitle) }}</h2>
@endif
@if ($sectionSubcopy)
<p class="font-body text-base leading-relaxed text-muted-foreground">{{ __($sectionSubcopy) }}</p>
@endif
</div>
@endif
<div
x-data="{ color: @js($swatches[0]['value'] ?? null), colors: @js($colorLabels) }"
class="grid grid-cols-1 items-center gap-10 lg:grid-cols-2 lg:gap-16"
>
{{-- ── Media placeholder ── --}}
<div class="relative flex aspect-[4/5] w-full items-center justify-center overflow-hidden rounded-lg bg-muted sm:aspect-square lg:aspect-[4/5]" role="img" aria-label="{{ __($name) }}">
@if ($eyebrow)
<span class="absolute start-5 top-5">
<x-ui.badge variant="secondary" class="font-body text-base uppercase tracking-widest">{{ __($eyebrow) }}</x-ui.badge>
</span>
@endif
<svg class="size-28 text-muted-foreground/40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M3 7l9-4 9 4-9 4-9-4Z" />
<path d="M3 7v10l9 4 9-4V7" />
<path d="M12 11v10" />
</svg>
</div>
{{-- ── Details ── --}}
<div class="flex flex-col items-start gap-6">
@if ($brand)
<p class="font-body text-base uppercase tracking-[0.3em] text-muted-foreground">{{ __($brand) }}</p>
@endif
<h3 class="font-heading text-balance text-3xl font-normal leading-[1.05] tracking-tight sm:text-4xl">{{ __($name) }}</h3>
<div class="flex items-baseline gap-4">
<span class="text-2xl font-semibold tracking-tight">{{ $price }}</span>
@if ($comparePrice)
<span class="text-base text-muted-foreground line-through">{{ $comparePrice }}</span>
@endif
</div>
<p class="max-w-md text-pretty font-body text-base leading-relaxed text-muted-foreground">{{ __($description) }}</p>
{{-- Colour swatches --}}
<fieldset class="flex flex-col gap-4">
<legend class="font-body text-base font-medium text-foreground">
{{ __('Colour') }}:
<span class="text-muted-foreground" x-text="colors[color]"></span>
</legend>
<x-ui.color-swatch name="color" model="color" :selected="$swatches[0]['value'] ?? null" :colors="$swatches" />
</fieldset>
{{-- Actions --}}
<div class="mt-2 flex w-full flex-col gap-4 sm:flex-row">
<x-ui.button :href="$primaryCta['href']" size="lg" class="sm:flex-1">
<x-slot:icon>
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="9" cy="21" r="1" />
<circle cx="20" cy="21" r="1" />
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6" />
</svg>
</x-slot:icon>
{{ __($primaryCta['label']) }}
</x-ui.button>
@if ($secondaryCta)
<x-ui.button :href="$secondaryCta['href']" variant="outline" size="lg" :withArrow="true">
{{ __($secondaryCta['label']) }}
</x-ui.button>
@endif
</div>
</div>
</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