# Border Beam Button

> shadcn/ui Button with an animated border beam, in text and icon variants, for primary calls to action in React apps.

Source: https://www.cult-ui.com/docs/components/border-beam-button

## Example

```tsx title="border-beam-button-demo.tsx"
"use client"

import { useEffect, useId, useState, type ReactNode } from "react"
import type { BorderBeamColorVariant } from "border-beam"
import { ArrowRight, Pause, Play, Sparkles, Wand2, Zap } from "lucide-react"

import { cn } from "@/lib/utils"
import {
  BorderBeamButton,
  BorderBeamIconButton,
} from "@/components/ui/border-beam-button"

const COLOR_ROW: { key: BorderBeamColorVariant; label: string }[] = [
  { key: "colorful", label: "Colorful" },
  { key: "ocean", label: "Ocean" },
  { key: "sunset", label: "Sunset" },
  { key: "mono", label: "Mono" },
]

const BEAM_SIZES = ["sm", "md", "line"] as const

function Section({
  title,
  description,
  children,
}: {
  title: string
  description?: string
  children: ReactNode
}) {
  return (
    <section className="space-y-4">
      <div className="space-y-1">
        <h3 className="font-semibold text-foreground text-sm tracking-tight">
          {title}
        </h3>
        {description ? (
          <p className="text-pretty text-muted-foreground text-xs leading-relaxed">
            {description}
          </p>
        ) : null}
      </div>
      {children}
    </section>
  )
}

export default function BorderBeamButtonDemo() {
  const id = useId()
  const [beamActive, setBeamActive] = useState(true)
  const [respectsReducedMotion, setRespectsReducedMotion] = useState(false)

  useEffect(() => {
    const mq = window.matchMedia("(prefers-reduced-motion: reduce)")
    const sync = () => setRespectsReducedMotion(mq.matches)
    sync()
    mq.addEventListener("change", sync)
    return () => mq.removeEventListener("change", sync)
  }, [])

  const effectiveActive = beamActive && !respectsReducedMotion

  return (
    <div className="mx-auto w-full max-w-3xl space-y-10 px-4 py-8 md:px-6">
      <header className="space-y-2 text-center">
        <p className="font-medium text-[11px] text-muted-foreground uppercase tracking-[0.2em]">
          Border beam
        </p>
        <h2 className="font-semibold text-foreground text-xl tracking-tight md:text-2xl">
          Button + traveling glow
        </h2>
        <p className="mx-auto max-w-lg text-pretty text-muted-foreground text-sm leading-relaxed">
          <span className="text-foreground/90">className</span> targets the
          shadcn <span className="text-foreground/90">Button</span>;{" "}
          <span className="text-foreground/90">borderBeamClassName</span> styles
          the beam wrapper. Use{" "}
          <span className="text-foreground/90">colorVariant</span>,{" "}
          <span className="text-foreground/90">beamSize</span>, and{" "}
          <span className="text-foreground/90">active</span> to tune the effect.
        </p>
      </header>

      <div className="">
        <Section
          description="Outline and secondary read well against the beam; default adds a stronger fill."
          title="Button variants"
        >
          <div className="flex flex-wrap items-center justify-center gap-3">
            <BorderBeamButton type="button" variant="default">
              Continue
            </BorderBeamButton>
            <BorderBeamButton type="button" variant="outline">
              Outline
            </BorderBeamButton>
            <BorderBeamButton type="button" variant="secondary">
              Secondary
            </BorderBeamButton>
            <BorderBeamButton type="button" variant="ghost">
              Ghost
            </BorderBeamButton>
          </div>
        </Section>
      </div>

      <div className="">
        <Section
          description="Same outline button; swap the beam palette."
          title="Beam color"
        >
          <div className="flex flex-wrap items-center justify-center gap-3">
            {COLOR_ROW.map(({ key, label }) => (
              <BorderBeamButton
                colorVariant={key}
                key={key}
                type="button"
                variant="outline"
              >
                {label}
              </BorderBeamButton>
            ))}
          </div>
        </Section>
      </div>

      <div className="">
        <Section
          description="sm keeps a tight glow on controls; md is fuller; line is a bottom traveling accent."
          title="Beam size"
        >
          <div className="flex flex-wrap items-center justify-center gap-3">
            {BEAM_SIZES.map((size) => (
              <BorderBeamButton
                beamSize={size}
                key={size}
                type="button"
                variant="outline"
              >
                <span className="font-mono text-xs">{size}</span>
              </BorderBeamButton>
            ))}
          </div>
        </Section>
      </div>

      <div className="">
        <Section
          description="Icon buttons default to icon sizing; pair with lucide icons."
          title="Icon buttons"
        >
          <div className="flex flex-wrap items-center justify-center gap-3">
            <BorderBeamIconButton
              aria-label="Sparkles"
              colorVariant="sunset"
              title="Sparkles"
              type="button"
              variant="outline"
            >
              <Sparkles aria-hidden className="size-3.5" />
            </BorderBeamIconButton>
            <BorderBeamIconButton
              aria-label="Zap"
              colorVariant="ocean"
              title="Zap"
              type="button"
              variant="secondary"
            >
              <Zap aria-hidden className="size-3.5" />
            </BorderBeamIconButton>
            <BorderBeamIconButton
              aria-label="Wand"
              colorVariant="mono"
              staticColors
              title="Wand"
              type="button"
              variant="outline"
            >
              <Wand2 aria-hidden className="size-3.5" />
            </BorderBeamIconButton>
          </div>
        </Section>
      </div>

      <div className="">
        <Section
          description="Toggle the beam without hiding the button. Respects prefers-reduced-motion."
          title="Interactive"
        >
          <div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
            <BorderBeamButton
              active={effectiveActive}
              className="gap-2 pr-5 pl-6"
              colorVariant="colorful"
              type="button"
              variant="outline"
            >
              Get started
              <ArrowRight aria-hidden className="size-4 opacity-80" />
            </BorderBeamButton>
            <BorderBeamIconButton
              aria-label={effectiveActive ? "Pause beam" : "Play beam"}
              aria-pressed={beamActive}
              colorVariant="ocean"
              onClick={() => setBeamActive((v) => !v)}
              title={effectiveActive ? "Pause beam" : "Play beam"}
              type="button"
              variant="secondary"
            >
              {effectiveActive ? (
                <Pause aria-hidden className="size-3.5" />
              ) : (
                <Play aria-hidden className="size-3.5" />
              )}
            </BorderBeamIconButton>
          </div>
          <p
            className={cn(
              "text-center text-xs",
              respectsReducedMotion
                ? "text-amber-600 dark:text-amber-400"
                : "text-muted-foreground"
            )}
            id={`${id}-hint`}
          >
            {respectsReducedMotion
              ? "Reduced motion is on — beam animation stays off."
              : "Tip: pause leaves the button fully clickable; the glow stops."}
          </p>
        </Section>
      </div>
    </div>
  )
}
```

BorderBeamButton is a React button component for shadcn/ui built on the border-beam package. Use it for the primary call to action in AI prompt bars, onboarding flows, or upgrade prompts.

## Installation

### CLI

```bash
npx shadcn@latest add @cult-ui/border-beam-button
```

### Manual

**Install the [Border Beam](https://www.npmjs.com/package/border-beam) package.**

```bash
pnpm add border-beam
```

**Add the [shadcn/ui Button](https://ui.shadcn.com/docs/components/button) if
  you do not already have it.**

**Copy and paste the following code into your project.**

```tsx title="border-beam-button.tsx"
"use client"

/**
 * `border-beam` around `Button` — compact `beamSize="sm"` glow for controls.
 * `className` styles the button; `borderBeamClassName` styles the beam wrapper.
 */
import type { ComponentProps, CSSProperties } from "react"
import { forwardRef } from "react"
import {
  BorderBeam,
  type BorderBeamProps,
  type BorderBeamSize,
} from "border-beam"

import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"

type BeamShellProps = Pick<
  BorderBeamProps,
  | "colorVariant"
  | "theme"
  | "staticColors"
  | "duration"
  | "active"
  | "borderRadius"
  | "brightness"
  | "saturation"
  | "hueRange"
  | "strength"
  | "onActivate"
  | "onDeactivate"
> & {
  beamSize?: BorderBeamSize
  borderBeamClassName?: string
  borderBeamStyle?: CSSProperties
}

export type BorderBeamButtonProps = ComponentProps<typeof Button> &
  BeamShellProps

export type BorderBeamIconButtonProps = BorderBeamButtonProps

export const BorderBeamButton = forwardRef<
  HTMLDivElement,
  BorderBeamButtonProps
>(function BorderBeamButton(
  {
    beamSize = "sm",
    borderBeamClassName,
    borderBeamStyle,
    theme = "auto",
    colorVariant,
    staticColors,
    duration,
    active,
    borderRadius,
    brightness,
    saturation,
    hueRange,
    strength,
    onActivate,
    onDeactivate,
    className,
    ...buttonProps
  },
  ref
) {
  return (
    <BorderBeam
      active={active}
      borderRadius={borderRadius}
      brightness={brightness}
      className={cn(
        "overflow-visible! inline-flex w-fit min-w-0 flex-col items-stretch leading-none",
        borderBeamClassName
      )}
      colorVariant={colorVariant}
      duration={duration}
      hueRange={hueRange}
      onActivate={onActivate}
      onDeactivate={onDeactivate}
      ref={ref}
      saturation={saturation}
      size={beamSize}
      staticColors={staticColors}
      strength={strength}
      style={borderBeamStyle}
      theme={theme}
    >
      <Button className={className} {...buttonProps} />
    </BorderBeam>
  )
})

BorderBeamButton.displayName = "BorderBeamButton"

export const BorderBeamIconButton = forwardRef<
  HTMLDivElement,
  BorderBeamIconButtonProps
>(function BorderBeamIconButton(
  { size = "icon-sm", className, ...props },
  ref
) {
  return (
    <BorderBeamButton
      className={cn("!leading-none [&_svg]:block [&_svg]:shrink-0", className)}
      ref={ref}
      size={size}
      {...props}
    />
  )
})

BorderBeamIconButton.displayName = "BorderBeamIconButton"
```

**Update the import paths to match your project setup.**

## Usage

`BorderBeamButton` wraps the standard `Button` with [`BorderBeam`](https://www.npmjs.com/package/border-beam) from the [`border-beam`](https://www.npmjs.com/package/border-beam) package. Pass any `Button` props as usual. Use `className` for the button surface and `borderBeamClassName` (or `borderBeamStyle`) for the beam wrapper.

- **BorderBeamButton** — default `beamSize="sm"` for a tight glow on controls.
- **BorderBeamIconButton** — same API, with `size` defaulting to `icon-sm` for icon-only actions.

```tsx
import {
  BorderBeamButton,
  BorderBeamIconButton,
} from "@/components/ui/border-beam-button";
import { Sparkles } from "lucide-react";
```

```tsx
<BorderBeamButton type="button" variant="outline">
  Outline
</BorderBeamButton>

<BorderBeamIconButton
  type="button"
  variant="outline"
  aria-label="Sparkles"
>
  <Sparkles className="size-3" />
</BorderBeamIconButton>
```

### Beam options

The component forwards the main [`BorderBeam`](https://www.npmjs.com/package/border-beam) controls: `beamSize`, `theme`, `colorVariant`, `staticColors`, `duration`, `active`, `borderRadius`, `brightness`, `saturation`, `hueRange`, `strength`, `onActivate`, and `onDeactivate`. See the [Border Beam documentation](https://beam.jakubantalik.com/) for behavior and examples.

## Credits

The animated border effect comes from **Border Beam** by Jakub Antalík. Documentation, demos, and the underlying React package are available at [beam.jakubantalik.com](https://beam.jakubantalik.com/).
