Skip to content
Brok UI

Loading…

No results

Featured Product

Open source

Single-product spotlights — a hero product with media, variant swatches and add-to-cart.

Preview

resources/views/blocks/featured-product-spotlight.blade.php Blade
@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

terminal
php artisan ui:add blocks/featured-product-spotlight

Installs these dependencies automatically:

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

button badge color-swatch

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.

featured-product-spotlight.md
# 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
root featured-product

Usage

Render the block as a component, passing data where useful:

resources/views/welcome.blade.php Blade
<x-blocks.featured-product-spotlight />

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/featured-product-spotlight.blade.php Blade
@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