Skip to content
Brok UI

Loading…

No results

Product Quick View

Open source

Quick-view dialog panels — a compact modal-style product card with image, pricing, rating and add-to-cart.

Preview

resources/views/blocks/product-quick-view-gallery.blade.php Blade
@props([
    'name'           => 'Studio Linen Overshirt',
    'brand'          => 'Marlowe & Co.',
    'price'          => '$189',
    'compareAtPrice' => '$240',
    'saleBadge'      => 'Sale',
    'rating'         => 4,
    'reviewCount'    => 94,
    'description'    => 'Relaxed open-collar overshirt in 100% washed linen. Slightly oversized with patch pockets and a straight hem.',
    'colours'        => null,
    'sizes'          => null,
    'detailsHref'    => '#',
    'closeHref'      => '#',
])

@php
    $colours ??= [['label' => 'Sand', 'token' => 'bg-muted'], ['label' => 'Slate', 'token' => 'bg-secondary'], ['label' => 'Dusk', 'token' => 'bg-primary/30'], ['label' => 'Bark', 'token' => 'bg-accent']];
    $sizes ??= ['XS', 'S', 'M', 'L', 'XL'];
    $thumbnails = [__('Front view'), __('Back view'), __('Detail view')];
@endphp

<div data-slot="quick-view" role="dialog" aria-modal="true" aria-label="{{ __('Product quick view') }}" {{ $attributes->merge(['class' => 'relative mx-auto w-full max-w-3xl overflow-hidden rounded-lg border border-border bg-card shadow-xl']) }}>
    <x-ui.button :href="$closeHref" variant="ghost" size="icon" class="absolute end-3 top-3 z-20 rounded-full text-muted-foreground hover:text-foreground" aria-label="{{ __('Close') }}">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-5" aria-hidden="true"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
    </x-ui.button>

    <div class="grid grid-cols-1 sm:grid-cols-2">
        <div class="flex flex-col gap-4 bg-muted/30 p-4 sm:p-6">
            <div class="relative aspect-square w-full overflow-hidden rounded-lg border border-border bg-gradient-to-br from-muted to-accent/30" role="img" aria-label="{{ __($name) }}">
                @if ($compareAtPrice && $saleBadge)
                    <x-ui.badge variant="destructive" class="absolute start-3 top-3 shadow-sm">{{ __($saleBadge) }}</x-ui.badge>
                @endif
            </div>
            <div class="flex gap-2" role="group" aria-label="{{ __('Product image thumbnails') }}">
                @foreach ($thumbnails as $i => $thumb)
                    <button type="button" aria-label="{{ $thumb }}" aria-pressed="{{ $i === 0 ? 'true' : 'false' }}" @class(['aspect-square w-1/3 overflow-hidden rounded-lg border bg-gradient-to-br from-muted to-background transition-shadow focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', 'border-primary ring-2 ring-primary' => $i === 0, 'border-border hover:border-muted-foreground/40' => $i !== 0])></button>
                @endforeach
            </div>
        </div>

        <div class="flex flex-col gap-6 p-6 sm:p-8">
            <div class="flex flex-col gap-2 pe-8">
                <p class="text-sm font-semibold uppercase tracking-widest text-muted-foreground">{{ __($brand) }}</p>
                <h2 class="text-pretty text-xl font-semibold leading-snug tracking-tight sm:text-2xl">{{ __($name) }}</h2>
            </div>
            {{-- The price props arrive pre-formatted, so <x-ui.price> renders them verbatim. --}}
            <x-ui.price :amount="$price" :compare-at="$compareAtPrice" size="lg" />
            <div class="flex items-center gap-2">
                <x-ui.rating :value="$rating" display size="sm" />
                <span class="text-sm text-muted-foreground">{{ __(':count reviews', ['count' => number_format($reviewCount)]) }}</span>
            </div>
            <p class="line-clamp-3 text-pretty text-sm leading-relaxed text-muted-foreground">{{ __($description) }}</p>
            <x-ui.separator />
            <fieldset class="flex flex-col gap-2">
                <legend class="text-sm font-medium text-foreground">{{ __('Colour') }}</legend>
                <x-ui.color-swatch name="colour" size="sm" :selected="$colours[0]['value'] ?? $colours[0]['label'] ?? null" :colors="$colours" />
            </fieldset>
            <fieldset class="flex flex-col gap-2">
                <legend class="text-sm font-medium text-foreground">{{ __('Size') }}</legend>
                <x-ui.option-group name="size" size="sm" :options="$sizes" :selected="$sizes[1] ?? null" />
            </fieldset>
            <div class="mt-auto flex flex-col gap-4 pt-2">
                <x-ui.button type="button" size="lg" class="w-full">
                    <x-slot:icon><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-5" aria-hidden="true"><circle cx="8" cy="21" r="1" /><circle cx="19" cy="21" r="1" /><path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12" /></svg></x-slot:icon>
                    {{ __('Add to cart') }}
                </x-ui.button>
                <x-ui.button :href="$detailsHref" variant="outline" size="lg" class="w-full">
                    {{ __('View full details') }}
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="ms-2 size-4 rtl:-scale-x-100" aria-hidden="true"><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
                </x-ui.button>
            </div>
        </div>
    </div>
</div>

Installation

terminal
php artisan ui:add blocks/product-quick-view-gallery

Registry contract

Install confidence

php artisan ui:add blocks/product-quick-view-gallery 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/product-quick-view-gallery.blade.php

Registry dependencies

button badge price rating separator color-swatch option-group

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.

product-quick-view-gallery.md
# Brok UI block: Product Quick View Gallery (`product-quick-view-gallery`)

A quick-view modal panel with an image gallery and thumbnail strip beside product info, colour and size selectors and add-to-cart.

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/product-quick-view-gallery
```

## Render it

```blade
<x-blocks.product-quick-view-gallery />
```

## Props

- `name` (string, default `Studio Linen Overshirt`) — Product name shown as the dialog heading.
- `brand` (string, default `Marlowe & Co.`) — Brand label shown above the product name.
- `price` (string, default `$189`) — Current price, passed straight to the price component.
- `compare-at-price` (string, default `$240`) — Original price shown next to the current price; falsy hides it.
- `sale-badge` (string, default `Sale`) — Badge shown on the image when compareAtPrice is set.
- `rating` (int, default `4`) — Average star rating value.
- `review-count` (int, default `94`) — Number of reviews shown next to the rating.
- `description` (string, default `Relaxed open-collar overshirt in 100% washed linen. Slightly oversized with patch pockets and a straight hem.`) — Short product description shown under the rating.
- `colours` (mixed|null, default `null`) — Colour swatch options; null falls back to a default four-colour sample set.
- `sizes` (mixed|null, default `null`) — Size options for the option-group selector; null falls back to a default sample set.
- `details-href` (string, default `#`) — Destination for the view-full-details button.
- `close-href` (string, default `#`) — Destination for the close button.

## Use when

- Opening a quick-view modal that pairs an image gallery with a thumbnail strip beside colour and size selectors and an add-to-cart action.
- Use when the product needs a colour swatch picker in addition to size, not just a single image and size selector.

## Avoid when

- Needing only a single product image with a size selector and no colour picker; use product-quick-view-modal instead.

## Rules

- Render the installed block with `<x-blocks.product-quick-view-gallery />` 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/product-quick-view-gallery
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/product-quick-view-gallery.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

  • Opening a quick-view modal that pairs an image gallery with a thumbnail strip beside colour and size selectors and an add-to-cart action.
  • Use when the product needs a colour swatch picker in addition to size, not just a single image and size selector.

Avoid when

  • Needing only a single product image with a size selector and no colour picker; use product-quick-view-modal instead.

Anti-patterns

  • Do not replace semantic props with conflicting utility classes.
  • Do not remove labels, focus styles, or state attributes.
Anatomy
dialog panel close button image with thumbnail strip price and rating colour swatch selector size selector add-to-cart and view-details buttons

Usage

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

resources/views/welcome.blade.php Blade
<x-blocks.product-quick-view-gallery />

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/product-quick-view-gallery.blade.php Blade
@props([
    'name'           => 'Studio Linen Overshirt',
    'brand'          => 'Marlowe & Co.',
    'price'          => '$189',
    'compareAtPrice' => '$240',
    'saleBadge'      => 'Sale',
    'rating'         => 4,
    'reviewCount'    => 94,
    'description'    => 'Relaxed open-collar overshirt in 100% washed linen. Slightly oversized with patch pockets and a straight hem.',
    'colours'        => null,
    'sizes'          => null,
    'detailsHref'    => '#',
    'closeHref'      => '#',
])

@php
    $colours ??= [['label' => 'Sand', 'token' => 'bg-muted'], ['label' => 'Slate', 'token' => 'bg-secondary'], ['label' => 'Dusk', 'token' => 'bg-primary/30'], ['label' => 'Bark', 'token' => 'bg-accent']];
    $sizes ??= ['XS', 'S', 'M', 'L', 'XL'];
    $thumbnails = [__('Front view'), __('Back view'), __('Detail view')];
@endphp

<div data-slot="quick-view" role="dialog" aria-modal="true" aria-label="{{ __('Product quick view') }}" {{ $attributes->merge(['class' => 'relative mx-auto w-full max-w-3xl overflow-hidden rounded-lg border border-border bg-card shadow-xl']) }}>
    <x-ui.button :href="$closeHref" variant="ghost" size="icon" class="absolute end-3 top-3 z-20 rounded-full text-muted-foreground hover:text-foreground" aria-label="{{ __('Close') }}">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-5" aria-hidden="true"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
    </x-ui.button>

    <div class="grid grid-cols-1 sm:grid-cols-2">
        <div class="flex flex-col gap-4 bg-muted/30 p-4 sm:p-6">
            <div class="relative aspect-square w-full overflow-hidden rounded-lg border border-border bg-gradient-to-br from-muted to-accent/30" role="img" aria-label="{{ __($name) }}">
                @if ($compareAtPrice && $saleBadge)
                    <x-ui.badge variant="destructive" class="absolute start-3 top-3 shadow-sm">{{ __($saleBadge) }}</x-ui.badge>
                @endif
            </div>
            <div class="flex gap-2" role="group" aria-label="{{ __('Product image thumbnails') }}">
                @foreach ($thumbnails as $i => $thumb)
                    <button type="button" aria-label="{{ $thumb }}" aria-pressed="{{ $i === 0 ? 'true' : 'false' }}" @class(['aspect-square w-1/3 overflow-hidden rounded-lg border bg-gradient-to-br from-muted to-background transition-shadow focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', 'border-primary ring-2 ring-primary' => $i === 0, 'border-border hover:border-muted-foreground/40' => $i !== 0])></button>
                @endforeach
            </div>
        </div>

        <div class="flex flex-col gap-6 p-6 sm:p-8">
            <div class="flex flex-col gap-2 pe-8">
                <p class="text-sm font-semibold uppercase tracking-widest text-muted-foreground">{{ __($brand) }}</p>
                <h2 class="text-pretty text-xl font-semibold leading-snug tracking-tight sm:text-2xl">{{ __($name) }}</h2>
            </div>
            {{-- The price props arrive pre-formatted, so <x-ui.price> renders them verbatim. --}}
            <x-ui.price :amount="$price" :compare-at="$compareAtPrice" size="lg" />
            <div class="flex items-center gap-2">
                <x-ui.rating :value="$rating" display size="sm" />
                <span class="text-sm text-muted-foreground">{{ __(':count reviews', ['count' => number_format($reviewCount)]) }}</span>
            </div>
            <p class="line-clamp-3 text-pretty text-sm leading-relaxed text-muted-foreground">{{ __($description) }}</p>
            <x-ui.separator />
            <fieldset class="flex flex-col gap-2">
                <legend class="text-sm font-medium text-foreground">{{ __('Colour') }}</legend>
                <x-ui.color-swatch name="colour" size="sm" :selected="$colours[0]['value'] ?? $colours[0]['label'] ?? null" :colors="$colours" />
            </fieldset>
            <fieldset class="flex flex-col gap-2">
                <legend class="text-sm font-medium text-foreground">{{ __('Size') }}</legend>
                <x-ui.option-group name="size" size="sm" :options="$sizes" :selected="$sizes[1] ?? null" />
            </fieldset>
            <div class="mt-auto flex flex-col gap-4 pt-2">
                <x-ui.button type="button" size="lg" class="w-full">
                    <x-slot:icon><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-5" aria-hidden="true"><circle cx="8" cy="21" r="1" /><circle cx="19" cy="21" r="1" /><path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12" /></svg></x-slot:icon>
                    {{ __('Add to cart') }}
                </x-ui.button>
                <x-ui.button :href="$detailsHref" variant="outline" size="lg" class="w-full">
                    {{ __('View full details') }}
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="ms-2 size-4 rtl:-scale-x-100" aria-hidden="true"><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
                </x-ui.button>
            </div>
        </div>
    </div>
</div>

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