Skip to content
Brok UI

Loading…

No results

Hover Reveal List

Open source

Hover-reveal index lists — large rows that swap a preview image on hover or focus.

Preview

resources/views/blocks/hover-reveal-list-index.blade.php Blade
@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

terminal
php artisan ui:add blocks/hover-reveal-list-index

Installs these dependencies automatically:

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

badge button container section separator

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.

hover-reveal-list-index.md
# 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
header with heading, subheading, and view-all button separator numbered row list row index number and title row category badge trailing chevron icon sticky desktop preview panel

Usage

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

resources/views/welcome.blade.php Blade
<x-blocks.hover-reveal-list-index />

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/hover-reveal-list-index.blade.php Blade
@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