Components Border Beam

Petal Pro is the full SaaS app this is built for

Auth, billing, admin, and Claude Code integration included. One purchase, unlimited projects.

Border Beam

An animated beam of light that travels the border of its container - the quiet way to draw the eye to a card, a CTA or an auth form. It's pure CSS (an offset-path animation), so there's no JavaScript and nothing to register. The wrapper is a proper panel now, so your content goes straight inside.
Basic Border Beam

Wrap your content and you get the panel plus a single beam on the default gradient.

Default border beam

heex
<.border_beam id="showcase-beam-basic" class="w-full max-w-md mx-auto">
<div class="flex items-center justify-center px-8 py-16">
<h3 class="text-2xl font-bold">Default border beam</h3>
</div>
</.border_beam>
Custom Colors

Set color_from and color_to for the gradient, and duration for the lap time.

Cyan beam

heex
<.border_beam
id="showcase-beam-cyan"
color_from="#22d3ee"
color_to="#0891b2"
duration="5s"
class="w-full max-w-md mx-auto"
>
<div class="flex items-center justify-center px-8 py-16">
<h3 class="text-2xl font-bold">Cyan beam</h3>
</div>
</.border_beam>
Beam with Content

No z-index dance - drop a full call-to-action straight inside the panel.

Ready when you are

Clone the repo, run the generators, ship by the weekend.

heex
<.border_beam
id="showcase-beam-content"
color_from="#8b5cf6"
color_to="#ec4899"
class="w-full max-w-md mx-auto"
>
<div class="flex flex-col items-center px-8 py-12 text-center">
<h3 class="text-2xl font-bold">Ready when you are</h3>
<p class="max-w-sm mt-2 text-gray-600 dark:text-gray-300">
Clone the repo, run the generators, ship by the weekend.
</p>
<.button label="Get started" class="mt-5" />
</div>
</.border_beam>
Pricing Cards

The classic use: run a beam around the plan you want people to pick.

Pro
$99/mo
heex
<.border_beam
id="showcase-beam-pricing"
color_from="#f59e0b"
color_to="#ef4444"
size="200px"
class="w-full max-w-xs mx-auto"
>
<div class="px-8 py-8 text-center">
<div class="text-sm font-semibold text-primary-600 dark:text-primary-400">Pro</div>
<div class="mt-2 text-4xl font-bold">
$99<span class="text-base font-normal text-gray-500">/mo</span>
</div>
<.button label="Choose Pro" class="w-full mt-6" />
</div>
</.border_beam>
Glow and Multiple Beams

glow swaps the sharp head for a soft comet - which unlocks the full beam length, since a long sharp beam clamps at the corners for safety. beams runs several at once.

Now playing
Stairway to Heaven - Led Zeppelin
2:458:02
heex
<.border_beam
id="showcase-beam-glow"
glow
beams={2}
size="400px"
duration="9s"
color_from="#f43f5e"
color_to="#3b82f6"
class="w-full max-w-sm mx-auto"
>
<div class="p-6">
<div class="font-semibold leading-none text-gray-900 dark:text-gray-100">Now playing</div>
<div class="mt-1.5 text-sm text-gray-500 dark:text-gray-400">
Stairway to Heaven - Led Zeppelin
</div>
<div class="w-40 h-40 mx-auto mt-5 rounded-lg bg-gradient-to-br from-purple-500 to-pink-500">
</div>
<div class="mt-5">
<.progress value={34} size="xs" />
</div>
<div class="flex justify-between mt-2 text-sm text-gray-500 dark:text-gray-400">
<span>2:45</span><span>8:02</span>
</div>
<div class="flex justify-center gap-3 mt-4">
<.button variant="outline" size="icon" radius="full" aria-label="Previous">
<.icon name="hero-backward" />
</.button>
<.button size="icon" radius="full" aria-label="Play">
<.icon name="hero-play" />
</.button>
<.button variant="outline" size="icon" radius="full" aria-label="Next">
<.icon name="hero-forward" />
</.button>
</div>
</div>
</.border_beam>
Newsletter Signup

A subtle beam turns a plain signup box into something worth stopping for.

Join the list

One email a week. No noise.

heex
<.border_beam
id="showcase-beam-newsletter"
color_from="#a855f7"
color_to="#6366f1"
duration="10s"
class="w-full max-w-md mx-auto"
>
<div class="px-8 py-10 text-center">
<h3 class="text-xl font-bold">Join the list</h3>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-300">One email a week. No noise.</p>
<div class="flex max-w-sm gap-2 mx-auto mt-5">
<.input type="email" name="email" value="" placeholder="you@example.com" class="flex-1" />
<.button label="Subscribe" />
</div>
</div>
</.border_beam>
Properties
Attribute Type Default Description
beams integer 1 number of beams, evenly phased around the border
border_radius string nil border radius of the container. When unset, follows the theme radius (--pc-radius, scaled for panels)
border_width string "1px" width of the border the beam runs along
class any nil extra classes for the container
color_from string "#ffaa40" start color of the beam gradient
color_to string "#9c40ff" end color of the beam gradient
delay string "0s" phase offset for the beam (applied as a head start)
duration string "8s" time the beam takes to travel the full border once
easing string "linear" beam motion: linear for constant speed, ease-in-out for a glide, spring for a springy lap with a settle at the seam. Spring applies to a single beam; with beams > 1 motion stays linear so the chase remains even
one of: "linear", "ease-in-out", "spring"
glow boolean false symmetric comet (transparent -> colours -> transparent) instead of the sharp-headed beam. Glows tolerate very long sizes, so the size clamp is lifted - great for long dramatic beams
initial_offset integer nil shifts the lap seam (0-100, % of the path from the top-left corner). The spring's pause-release happens at the seam, so this sets WHERE it springs from. Defaults to 0; spring easing defaults to 25, which parks the release near the top centre on typical shapes
rest global
reverse boolean false run the beam anticlockwise
size string "150px" length of the beam. Also sets how widely the beam swings through corners
:inner_block slot slot