Beam

Soft edge light that travels around an outline on its own.

A traveling highlight on the outline of a surface. Use it on cards, outline buttons, and outline badges. Tune the colors and duration.

Cards

Token ready
Restyle every surface with your theme.
Copy and ship
Drop a piece in and keep moving.
Quiet edges
A soft beam that travels the outline on its own.
Color 1
Color 2
Color 3
8s
28
tsx
import { Beam } from "@/components/motion/beam"import {  Card,  CardDescription,  CardHeader,  CardTitle,} from "@/components/ui/card"export function Example() {  return (    <div className="grid w-full max-w-lg grid-cols-1 gap-3 sm:grid-cols-2">      <Beam        colors={["#78c8ff", "#be8cff", "#ffb48c"]}        duration={8}        size={28}      >        <Card className="border-0 bg-transparent shadow-none hover:bg-transparent dark:hover:bg-transparent">          <CardHeader>            <CardTitle>Token ready</CardTitle>            <CardDescription>              Restyle every surface with your theme.            </CardDescription>          </CardHeader>        </Card>      </Beam>      <Beam        colors={["#78c8ff", "#be8cff", "#ffb48c"]}        duration={8}        size={28}      >        <Card className="border-0 bg-transparent shadow-none hover:bg-transparent dark:hover:bg-transparent">          <CardHeader>            <CardTitle>Copy and ship</CardTitle>            <CardDescription>              Drop a piece in and keep moving.            </CardDescription>          </CardHeader>        </Card>      </Beam>    </div>  )}

Buttons

Outline only. Hover lights the full outline.

Color 1
Color 2
Color 3
8s
28
tsx
import { Beam } from "@/components/motion/beam"import { Button } from "@/components/ui/button"export function Example() {  return (    <div className="flex flex-wrap items-center justify-center gap-3">      <Beam        colors={["#78c8ff", "#be8cff", "#ffb48c"]}        duration={8}        size={28}        hoverFull      >        <Button variant="outline" className="border-0 bg-transparent shadow-none hover:bg-transparent dark:hover:bg-transparent">          Continue        </Button>      </Beam>      <Beam        colors={["#78c8ff", "#be8cff", "#ffb48c"]}        duration={8}        size={28}        hoverFull      >        <Button variant="outline" className="border-0 bg-transparent shadow-none hover:bg-transparent dark:hover:bg-transparent">          Get started        </Button>      </Beam>    </div>  )}

Badges

Outline only.

New
Live
Beta
Color 1
Color 2
Color 3
8s
28
tsx
import { Beam } from "@/components/motion/beam"import { Badge } from "@/components/ui/badge"export function Example() {  return (    <div className="flex flex-wrap items-center justify-center gap-3">      <Beam        colors={["#78c8ff", "#be8cff", "#ffb48c"]}        duration={8}        size={28}      >        <Badge variant="outline" className="h-6 px-2.5 border-0 bg-transparent shadow-none hover:bg-transparent dark:hover:bg-transparent">          New        </Badge>      </Beam>      <Beam        colors={["#78c8ff", "#be8cff", "#ffb48c"]}        duration={8}        size={28}      >        <Badge variant="outline" className="h-6 px-2.5 border-0 bg-transparent shadow-none hover:bg-transparent dark:hover:bg-transparent">          Live        </Badge>      </Beam>    </div>  )}

Grid

A bento layout where the outline lights up under the pointer. The glow reaches past each tile so neighbors catch it across the gaps.

  • Token ready

    Restyle every tile with your theme tokens.

  • Copy and ship

    Install a tile and drop it into a page.

  • Compose

    Mix sections freely.

  • Accessible

    Keyboard first defaults.

  • Rapid development

    Ship interfaces that feel finished.

  • Theme sync

    Light and dark out of the box.

Color 1
Color 2
Color 3
16rem
tsx
"use client"import { useRef, type MouseEvent } from "react"import { cn } from "@/lib/utils"const TILES = [  {    title: "Token ready",    description: "Restyle every tile with your theme tokens.",    image: "/assets/brand/demos/attachments/attachment-1.png",    className: "sm:col-span-2 sm:row-span-2",  },  {    title: "Copy and ship",    description: "Install a tile and drop it into a page.",    image: "/assets/brand/demos/attachments/attachment-2.png",    className: "sm:col-span-2",  },  {    title: "Compose",    description: "Mix sections freely.",    image: "/assets/brand/demos/attachments/attachment-3.png",    className: "",  },  {    title: "Accessible",    description: "Keyboard first defaults.",    image: "/assets/brand/demos/attachments/attachment-4.png",    className: "",  },  {    title: "Rapid development",    description: "Ship interfaces that feel finished.",    image: "/assets/brand/demos/attachments/attachment-5.png",    className: "sm:col-span-2",  },  {    title: "Theme sync",    description: "Light and dark out of the box.",    image: "/assets/brand/demos/attachments/attachment-2.png",    className: "sm:col-span-2",  },]const GLOW_REACH_PX = 160const GLOW_RADIUS = "16rem"export function Example() {  const gridRef = useRef<HTMLUListElement>(null)  function syncGlow(clientX: number, clientY: number) {    const grid = gridRef.current    if (!grid) return    for (const el of grid.querySelectorAll<HTMLElement>("[data-glow-tile]")) {      const rect = el.getBoundingClientRect()      const x = ((clientX - rect.left) / Math.max(rect.width, 1)) * el.offsetWidth      const y = ((clientY - rect.top) / Math.max(rect.height, 1)) * el.offsetHeight      el.style.setProperty("--glow-x", `${x}px`)      el.style.setProperty("--glow-y", `${y}px`)      const near =        clientX >= rect.left - GLOW_REACH_PX &&        clientX <= rect.right + GLOW_REACH_PX &&        clientY >= rect.top - GLOW_REACH_PX &&        clientY <= rect.bottom + GLOW_REACH_PX      el.dataset.glow = near ? "on" : "off"    }  }  function onLeave() {    const grid = gridRef.current    if (!grid) return    for (const el of grid.querySelectorAll<HTMLElement>("[data-glow-tile]")) {      el.dataset.glow = "off"    }  }  return (    <ul      ref={gridRef}      onMouseMove={(event: MouseEvent<HTMLUListElement>) =>        syncGlow(event.clientX, event.clientY)      }      onMouseLeave={onLeave}      className="grid w-full max-w-2xl grid-cols-1 gap-3 sm:auto-rows-[10rem] sm:grid-cols-4"    >      {TILES.map((tile) => (        <li          key={tile.title}          data-glow-tile          data-glow="off"          className={cn(            "group relative min-h-40 overflow-hidden rounded-xl bg-[var(--border)] p-px sm:min-h-0",            tile.className,          )}        >          <span            aria-hidden            className="pointer-events-none absolute inset-0 z-[1] rounded-[inherit] opacity-0 transition-opacity duration-300 group-data-[glow=on]:opacity-100"            style={{              background: `radial-gradient(${GLOW_RADIUS} circle at var(--glow-x, 50%) var(--glow-y, 50%), #78c8ff, #be8cff, #ffb48c, transparent 55%)`,            }}          />          <div className="relative z-10 flex size-full flex-col overflow-hidden rounded-[calc(var(--radius-xl)-1px)] bg-background p-2">            <div className="relative min-h-0 flex-1 overflow-hidden rounded-sm bg-muted">              <img                src={tile.image}                alt=""                className="absolute inset-0 size-full object-cover"              />            </div>            <div className="px-1 pt-3 pb-1">              <h3 className="text-sm font-medium tracking-wide text-foreground">                {tile.title}              </h3>              <p className="mt-1 text-xs tracking-wide text-muted-foreground">                {tile.description}              </p>            </div>          </div>        </li>      ))}    </ul>  )}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add beam

Usage

import { Beam } from "@/components/motion/beam"import { Button } from "@/components/ui/button"
<Beam hoverFull>  <Button variant="outline" className="border-0 bg-transparent shadow-none">    Continue  </Button></Beam>

API Reference

Beam

PropTypeDefault
colors[string, string, string]["#78c8ff", "#be8cff", "#ffb48c"]
durationnumber8
sizenumber28
borderWidthnumber1
hoverFullbooleanfalse
classNamestring—
childrenReactNode—

Also accepts the other native div attributes. size is the lit arc length as a percent of the outline (clamped 10-80).