Skip to content

Shine Border

An animated border with one color or several colors.

Login

Enter your credentials to access your account.

Demo only. No data is sent.

View source on GitHub

Installation

Copy the component files below into src/components/spark-ui/shine-border/. Utility imports use @/lib/utils.

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

interface ShineBorderProps {
  borderWidth?: number;
  duration?: number;
  shineColor?: string | string[];
  className?: string;
}

const props = withDefaults(defineProps<ShineBorderProps>(), {
  borderWidth: 1,
  duration: 14,
  shineColor: "#000000",
});

const borderStyle = computed(() => ({
  "--border-width": `${props.borderWidth}px`,
  "--duration": `${props.duration}s`,
  backgroundImage: `radial-gradient(transparent, transparent, ${Array.isArray(props.shineColor) ? props.shineColor.join(",") : props.shineColor}, transparent, transparent)`,
}));
</script>

<template>
  <div
    aria-hidden="true"
    :class="
      cn(
        'shine-border pointer-events-none absolute inset-0 h-full w-full rounded-[inherit] will-change-[background-position]',
        props.className,
      )
    "
    :style="borderStyle"
  />
</template>

<style scoped>
.shine-border {
  padding: var(--border-width);
  background-size: 300% 300%;
  -webkit-mask:
    linear-gradient(#fff 0 0) content-box,
    linear-gradient(#fff 0 0);
  mask:
    linear-gradient(#fff 0 0) content-box,
    linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}

@media (prefers-reduced-motion: no-preference) {
  .shine-border {
    animation: shine var(--duration) infinite linear;
  }
}

@keyframes shine {
  0%,
  100% {
    background-position: 0% 0%;
  }
  50% {
    background-position: 100% 100%;
  }
}
</style>

Usage

vue
<script setup lang="ts">
import ShineBorder from "@/components/spark-ui/shine-border/shine-border.vue";
</script>

<template>
  <div class="relative rounded-xl p-8">
    <ShineBorder :shine-color="['#A07CFE', '#FE8FB5', '#FFBE7B']" :border-width="2" />
    <p>Your content stays interactive.</p>
  </div>
</template>

Props

PropTypeDefaultDescription
borderWidthnumber1The border width in pixels.
durationnumber14The animation duration in seconds.
shineColorstring | string[]"#000000"One CSS color or an array of CSS colors.
classNamestringNoneExtra classes for the border.
styleVue style valueNoneExtra styles that override the default styles.

Behavior

Place the border inside a parent with position: relative and a border radius. The border fills its parent and inherits its rounded corners. Place content next to the border, not inside it. This decorative component has no content slot.

The border does not receive pointer events, so links, inputs, and buttons remain interactive. Changes to the colors, width, and duration update while mounted. The animation stops when the user requests reduced motion. The border stays visible.

Native div attributes, Vue class, and Vue style pass to the border. The demo form only shows a local message. It does not send credentials.

Source

This component is a Vue port of Magic UI Shine Border. The upstream source defines the original behavior.

Released under the MIT License.