Components

Animated Beam

An SVG line with a travelling light gradient connecting two elements.

php artisan blatui:add animated-beam
Your device
Cloud
{{-- Two icon tiles connected by a beam that continuously "syncs" between them. --}}
<x-ui.animated-beam from="#beam-a" to="#beam-b" class="mx-auto max-w-md py-10">
    <div class="flex items-center justify-between px-6">
        <div
            id="beam-a"
            class="bg-background text-foreground flex size-14 items-center justify-center rounded-xl border shadow-sm"
        >
            <x-lucide-laptop class="size-6" aria-hidden="true" />
            <span class="sr-only">Your device</span>
        </div>

        <div
            id="beam-b"
            class="bg-background text-foreground flex size-14 items-center justify-center rounded-xl border shadow-sm"
        >
            <x-lucide-cloud class="size-6" aria-hidden="true" />
            <span class="sr-only">Cloud</span>
        </div>
    </div>
</x-ui.animated-beam>

Multi

S1
S2
Hub
S3
{{--
    Hub-and-spoke: a central node connected to three satellites by three beams.

    Each <x-ui.animated-beam> measures endpoints WITHIN its own root, so we stack three
    instances that all contain the same layout. The first layer renders the visible tiles;
    the others are absolutely overlaid and their tiles are made `invisible` (they still take
    up layout, so their centres line up exactly with the base layer for correct measurement).

    Endpoints are marked with data-* attributes (not ids) so the three stacked copies don't
    create duplicate-id a11y violations. The component accepts any CSS selector for from/to.
--}}
@php
    // The shared hub-and-spoke layout, reused by every beam layer so endpoints align.
    $hubLayout = function (bool $visible) {
        $tile = $visible ? '' : 'invisible';
        return <<<HTML
        <div class="grid grid-cols-3 items-center gap-8">
            <div class="flex flex-col gap-8">
                <div data-node="sat-1" class="{$tile} bg-background text-foreground flex size-12 items-center justify-center rounded-xl border shadow-sm">S1</div>
                <div data-node="sat-2" class="{$tile} bg-background text-foreground flex size-12 items-center justify-center rounded-xl border shadow-sm">S2</div>
            </div>
            <div class="flex justify-center">
                <div data-node="center" class="{$tile} bg-primary text-primary-foreground flex size-16 items-center justify-center rounded-2xl shadow-md">Hub</div>
            </div>
            <div class="flex justify-center">
                <div data-node="sat-3" class="{$tile} bg-background text-foreground flex size-12 items-center justify-center rounded-xl border shadow-sm">S3</div>
            </div>
        </div>
        HTML;
    };
@endphp

<div class="relative mx-auto max-w-md py-6">
    {{-- Base layer: visible tiles + the first beam (hub -> satellite 1). --}}
    <x-ui.animated-beam from="[data-node=center]" to="[data-node=sat-1]" :curvature="-30" class="relative">
        {!! $hubLayout(true) !!}
    </x-ui.animated-beam>

    {{-- Overlay beams: same layout (tiles invisible), each drawing one more spoke. --}}
    <x-ui.animated-beam from="[data-node=center]" to="[data-node=sat-2]" :curvature="30" :duration="3.6" class="absolute inset-0">
        {!! $hubLayout(false) !!}
    </x-ui.animated-beam>

    <x-ui.animated-beam from="[data-node=center]" to="[data-node=sat-3]" :duration="2.4" class="absolute inset-0">
        {!! $hubLayout(false) !!}
    </x-ui.animated-beam>
</div>