General
Scroll Spy code
Copy the implementation or inspect every file that belongs to this component unit.
resources/views/components/ui/scroll-spy.blade.php
@props([
'items' => [],
'offset' => 96,
])
@php
/*
| Anchor nav that highlights the section currently in view. Pass `items` as
| [['id' => 'usage', 'label' => 'Usage'], ...] — matching element ids must
| exist on the page. Highlighting is driven by an IntersectionObserver.
|
| Usage:
| <x-ui.scroll-spy :items="[
| ['id' => 'overview', 'label' => 'Overview'],
| ['id' => 'usage', 'label' => 'Usage'],
| ]" />
*/
$normalized = collect($items)->map(fn ($item, $key) => is_array($item)
? ['id' => (string) ($item['id'] ?? $key), 'label' => (string) ($item['label'] ?? $key)]
: ['id' => (string) $key, 'label' => (string) $item]
)->values();
@endphp
<nav
x-data="{
active: null,
ids: @js($normalized->pluck('id')->all()),
init() {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) { this.active = entry.target.id; }
});
}, { rootMargin: '-{{ (int) $offset }}px 0px -60% 0px' });
this.ids.forEach((id) => {
const el = document.getElementById(id);
if (el) { observer.observe(el); }
});
},
}"
aria-label="On this page"
{{ $attributes->merge(['class' => 'space-y-0.5']) }}
>
@foreach ($normalized as $item)
<a
href="#{{ $item['id'] }}"
:class="active === @js($item['id'])
? 'border-brand-500 text-brand-600 dark:border-brand-400 dark:text-brand-400 font-medium'
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-700 dark:hover:text-gray-200'"
class="block border-s-2 py-1.5 ps-3 text-sm transition"
>{{ $item['label'] }}</a>
@endforeach
</nav>