Skip to content

Border Beam

An animated beam of light which travels along the border of its container.

Login

Enter your credentials to access your account.

Installation

Copy and paste the following code into your project:

vue
<script setup lang="ts">
import type { CSSProperties } from "vue";
import { computed } from "vue";
import { cn } from "@/lib/utils";

interface BorderBeamProps {
  /** The class name of the border beam. */
  class?: string;
  /** The size of the border beam. */
  size?: number;
  /** The duration of the border beam animation in seconds. */
  duration?: number;
  /** The delay of the border beam animation in seconds. */
  delay?: number;
  /** The start color of the border beam gradient. */
  colorFrom?: string;
  /** The end color of the border beam gradient. */
  colorTo?: string;
  /** Custom inline styles applied to the beam. */
  style?: CSSProperties;
  /** Whether to reverse the animation direction. */
  reverse?: boolean;
  /** The initial offset position (0-100). */
  initialOffset?: number;
  /** The border width of the beam. */
  borderWidth?: number;
}

const props = withDefaults(defineProps<BorderBeamProps>(), {
  size: 50,
  duration: 6,
  delay: 0,
  colorFrom: "#ffaa40",
  colorTo: "#9c40ff",
  reverse: false,
  initialOffset: 0,
  borderWidth: 1,
});

const containerStyle = computed<CSSProperties>(() => ({
  "--border-beam-width": `${props.borderWidth}px`,
  borderWidth: "var(--border-beam-width)",
  borderStyle: "solid",
  maskImage: "linear-gradient(transparent, transparent), linear-gradient(#000, #000)",
  WebkitMaskImage: "linear-gradient(transparent, transparent), linear-gradient(#000, #000)",
  maskClip: "padding-box, border-box",
  WebkitMaskClip: "padding-box, border-box",
  maskComposite: "intersect",
  WebkitMaskComposite: "source-in, xor",
}));

const beamStyle = computed<CSSProperties>(() => ({
  "--color-from": props.colorFrom,
  "--color-to": props.colorTo,
  "--duration": props.duration,
  "--delay": props.delay,
  "--initial-offset": props.initialOffset,
  width: `${props.size}px`,
  offsetPath: `rect(0 auto auto 0 round ${props.size}px)`,
  animationDelay: "calc((var(--delay) + var(--initial-offset) / 100 * var(--duration)) * -1s)",
  animationDirection: props.reverse ? "reverse" : "normal",
  ...props.style,
}));
</script>

<template>
  <div
    class="pointer-events-none absolute inset-0 rounded-[inherit] border-transparent"
    :style="containerStyle"
  >
    <div
      :class="
        cn(
          'animate-border-beam absolute aspect-square bg-gradient-to-l from-[color:var(--color-from)] via-[color:var(--color-to)] to-transparent',
          props.class,
        )
      "
      :style="beamStyle"
    />
  </div>
</template>

<style scoped>
.animate-border-beam {
  animation: BorderBeam calc(var(--duration) * 1s) infinite linear;
}

@keyframes BorderBeam {
  to {
    offset-distance: 100%;
  }
}
</style>

Add the following keyframes and animation to your tailwind.config.js file:

tailwind.config.js
js
module.exports = {
  theme: {
    extend: {
      keyframes: {
        "border-beam": {
          to: { "offset-distance": "100%" },
        },
      },
      animation: {
        "border-beam": "border-beam calc(var(--duration) * 1s) infinite linear",
      },
    },
  },
};

Examples

Multiple Beams

Now Playing

Stairway to Heaven - Led Zeppelin

2:458:02

Reverse

Login

Enter your credentials to access your account.

Button

Props

PropTypeDefaultDescription
classstring-Custom class applied to the beam.
sizenumber50Size of the beam.
durationnumber6Duration of the animation in seconds.
delaynumber0Delay before the animation starts.
colorFromstring#ffaa40Start color of the beam gradient.
colorTostring#9c40ffEnd color of the beam gradient.
styleCSSProperties-Custom inline styles applied to the beam.
reversebooleanfalseWhether to reverse the animation.
initialOffsetnumber0Initial offset position (0-100).
borderWidthnumber1Border width of the beam.

Released under the MIT License.