Hover Reveal List
Hover-reveal index lists — large rows that swap a preview image on hover or focus.
Preview
@props([
'heading' => 'Selected work',
'subheading' => 'A few recent engagements. Hover a project to preview it.',
'viewAll' => 'View all projects',
'viewAllHref' => '#',
'items' => null,
])
@php
$items ??= [
[
'title' => 'Northwind Commerce',
'category' => 'E-commerce platform',
'href' => '#',
],
[
'title' => 'Atlas Analytics',
'category' => 'Data dashboard',
'href' => '#',
],
[
'title' => 'Cadence Health',
'category' => 'Mobile app',
'href' => '#',
],
[
'title' => 'Meridian Studio',
'category' => 'Brand & identity',
'href' => '#',
],
];
@endphp
<x-ui.section
x-data="{ active: 0 }"
data-slot="hover-reveal-list-01"
space="xl" {{ $attributes }}
>
<x-ui.container gutter="sm">
{{-- Header --}}
<div class="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div class="flex max-w-xl flex-col gap-2">
<h2 class="text-pretty text-3xl font-semibold tracking-tight sm:text-4xl">{{ __($heading) }}</h2>
<p class="text-pretty text-sm leading-relaxed text-muted-foreground">{{ __($subheading) }}</p>
</div>
<x-ui.button :href="$viewAllHref" variant="outline" :with-arrow="true" class="w-fit shrink-0">{{ __($viewAll) }}</x-ui.button>
</div>
<x-ui.separator class="mt-8" />
<div class="mt-8 grid grid-cols-1 gap-10 lg:grid-cols-2">
{{-- Left: index list --}}
<ul role="list" class="flex flex-col">
@foreach ($items as $index => $item)
<li class="border-b border-border last:border-b-0">
<a
href="{{ $item['href'] ?? '#' }}"
x-on:mouseenter="active = {{ $index }}"
x-on:focus="active = {{ $index }}"
class="group flex items-center gap-4 rounded-md py-6 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background sm:gap-6"
>
<span
class="font-mono text-sm tabular-nums transition-colors"
:class="active === {{ $index }} ? 'text-primary' : 'text-muted-foreground'"
>
{{ str_pad($index + 1, 2, '0', STR_PAD_LEFT) }}
</span>
{{-- Mobile inline thumbnail --}}
<span class="block aspect-[4/3] w-20 shrink-0 overflow-hidden rounded-md bg-muted lg:hidden" aria-hidden="true"></span>
<span class="flex min-w-0 flex-1 flex-col gap-2">
<span
class="truncate text-xl font-semibold tracking-tight transition-all duration-300 group-hover:translate-x-1 rtl:group-hover:-translate-x-1 sm:text-2xl"
:class="active === {{ $index }} ? 'text-foreground' : 'text-muted-foreground'"
>
{{ __($item['title']) }}
</span>
<x-ui.badge variant="secondary" class="w-fit">{{ __($item['category']) }}</x-ui.badge>
</span>
<svg
class="size-5 shrink-0 text-muted-foreground transition-transform duration-300 group-hover:translate-x-1 rtl:-scale-x-100 rtl:group-hover:-translate-x-1"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="m9 18 6-6-6-6" />
</svg>
</a>
</li>
@endforeach
</ul>
{{-- Right: sticky floating preview (desktop only) --}}
<div class="hidden lg:block" aria-hidden="true">
<div class="sticky top-24">
<div class="relative aspect-[4/3] w-full overflow-hidden rounded-lg border border-border bg-muted">
@foreach ($items as $index => $item)
<div
x-show="active === {{ $index }}"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 scale-[0.98]"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-muted"
>
<svg class="size-12 text-muted-foreground/60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect width="18" height="18" x="3" y="3" rx="2" />
<circle cx="9" cy="9" r="2" />
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
</svg>
<span class="text-sm font-medium text-muted-foreground">{{ __($item['title']) }}</span>
</div>
@endforeach
</div>
</div>
</div>
</div>
</x-ui.container>
</x-ui.section>
Installation
php artisan ui:add blocks/hover-reveal-list-index
Registry contract
Install confidence
php artisan ui:add blocks/hover-reveal-list-index
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.0.5
- License
- open
- Stability
- stable
- Contract
- v1
- Foundation
- ≥ 1.0.0
| Type | Generated target |
|---|---|
| blade | resources/views/blocks/hover-reveal-list-index.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: Hover Reveal List (`hover-reveal-list-index`)
An interactive two-column index list for portfolios, services, or menus: the left column is a vertical stack of large numbered rows (index, bold title, a category badge, and a chevron) separated by borders, and the right column is a sticky floating preview panel whose image swaps to the currently hovered or focused row. Each row is a real keyboard-focusable link that drives the reveal on hover and focus, the active title brightens and nudges, and on mobile every row shows its own inline thumbnail while the floating panel is hidden. Includes a heading, muted subline, and a View all button.
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/hover-reveal-list-index
```
## Render it
```blade
<x-blocks.hover-reveal-list-index />
```
## Props
- `heading` (string, default `Selected work`) — Section heading above the index list.
- `subheading` (string, default `A few recent engagements. Hover a project to preview it.`) — Muted supporting line under the heading.
- `view-all` (string, default `View all projects`) — Label for the trailing view-all button.
- `view-all-href` (string, default `#`) — Destination for the view-all button.
- `items` (mixed|null, default `null`) — Array of {title, category, href} rows; falls back to a 4-item sample portfolio list when null.
## Use when
- Use it to present a short curated list — portfolio projects, services, or menu items — as a vertical numbered index where hovering or focusing a row reveals a matching preview panel on desktop.
- Reach for it when each row needs to be a real, keyboard-focusable link and the reveal should follow genuine hover/focus state rather than a static side-by-side layout.
## Avoid when
- Avoid it when every item's visual needs to be shown at once without a hover/focus interaction; use a static card grid like industries instead.
## Rules
- Render the installed block with `<x-blocks.hover-reveal-list-index />` 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/hover-reveal-list-index
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/hover-reveal-list-index.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
- Use it to present a short curated list — portfolio projects, services, or menu items — as a vertical numbered index where hovering or focusing a row reveals a matching preview panel on desktop.
- Reach for it when each row needs to be a real, keyboard-focusable link and the reveal should follow genuine hover/focus state rather than a static side-by-side layout.
Avoid when
- Avoid it when every item's visual needs to be shown at once without a hover/focus interaction; use a static card grid like industries 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.hover-reveal-list-index />
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([
'heading' => 'Selected work',
'subheading' => 'A few recent engagements. Hover a project to preview it.',
'viewAll' => 'View all projects',
'viewAllHref' => '#',
'items' => null,
])
@php
$items ??= [
[
'title' => 'Northwind Commerce',
'category' => 'E-commerce platform',
'href' => '#',
],
[
'title' => 'Atlas Analytics',
'category' => 'Data dashboard',
'href' => '#',
],
[
'title' => 'Cadence Health',
'category' => 'Mobile app',
'href' => '#',
],
[
'title' => 'Meridian Studio',
'category' => 'Brand & identity',
'href' => '#',
],
];
@endphp
<x-ui.section
x-data="{ active: 0 }"
data-slot="hover-reveal-list-01"
space="xl" {{ $attributes }}
>
<x-ui.container gutter="sm">
{{-- Header --}}
<div class="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div class="flex max-w-xl flex-col gap-2">
<h2 class="text-pretty text-3xl font-semibold tracking-tight sm:text-4xl">{{ __($heading) }}</h2>
<p class="text-pretty text-sm leading-relaxed text-muted-foreground">{{ __($subheading) }}</p>
</div>
<x-ui.button :href="$viewAllHref" variant="outline" :with-arrow="true" class="w-fit shrink-0">{{ __($viewAll) }}</x-ui.button>
</div>
<x-ui.separator class="mt-8" />
<div class="mt-8 grid grid-cols-1 gap-10 lg:grid-cols-2">
{{-- Left: index list --}}
<ul role="list" class="flex flex-col">
@foreach ($items as $index => $item)
<li class="border-b border-border last:border-b-0">
<a
href="{{ $item['href'] ?? '#' }}"
x-on:mouseenter="active = {{ $index }}"
x-on:focus="active = {{ $index }}"
class="group flex items-center gap-4 rounded-md py-6 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background sm:gap-6"
>
<span
class="font-mono text-sm tabular-nums transition-colors"
:class="active === {{ $index }} ? 'text-primary' : 'text-muted-foreground'"
>
{{ str_pad($index + 1, 2, '0', STR_PAD_LEFT) }}
</span>
{{-- Mobile inline thumbnail --}}
<span class="block aspect-[4/3] w-20 shrink-0 overflow-hidden rounded-md bg-muted lg:hidden" aria-hidden="true"></span>
<span class="flex min-w-0 flex-1 flex-col gap-2">
<span
class="truncate text-xl font-semibold tracking-tight transition-all duration-300 group-hover:translate-x-1 rtl:group-hover:-translate-x-1 sm:text-2xl"
:class="active === {{ $index }} ? 'text-foreground' : 'text-muted-foreground'"
>
{{ __($item['title']) }}
</span>
<x-ui.badge variant="secondary" class="w-fit">{{ __($item['category']) }}</x-ui.badge>
</span>
<svg
class="size-5 shrink-0 text-muted-foreground transition-transform duration-300 group-hover:translate-x-1 rtl:-scale-x-100 rtl:group-hover:-translate-x-1"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="m9 18 6-6-6-6" />
</svg>
</a>
</li>
@endforeach
</ul>
{{-- Right: sticky floating preview (desktop only) --}}
<div class="hidden lg:block" aria-hidden="true">
<div class="sticky top-24">
<div class="relative aspect-[4/3] w-full overflow-hidden rounded-lg border border-border bg-muted">
@foreach ($items as $index => $item)
<div
x-show="active === {{ $index }}"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 scale-[0.98]"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-muted"
>
<svg class="size-12 text-muted-foreground/60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect width="18" height="18" x="3" y="3" rx="2" />
<circle cx="9" cy="9" r="2" />
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
</svg>
<span class="text-sm font-medium text-muted-foreground">{{ __($item['title']) }}</span>
</div>
@endforeach
</div>
</div>
</div>
</div>
</x-ui.container>
</x-ui.section>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.0.5 - Status
- Stable
- License
-
open - Deprecation
- Not deprecated
- Contract
-
v1 - Foundation
-
≥ 1.0.0