Neon Gradient Card

A beautiful neon card effect

Credits

Shout out to Magic UI for the implementation. I actually discovered this component while browsing their website.

Getting Started

Create the Component

Create the the NeonGradientCard component in the components directory.

<template>
  <div
    ref="containerRef"
    :style="
      {
        '--border-size': `${borderSize}px`,
        '--border-radius': `${borderRadius}px`,
        '--neon-first-color': neonColors.firstColor,
        '--neon-second-color': neonColors.secondColor,
        '--card-width': `${dimensions.width}px`,
        '--card-height': `${dimensions.height}px`,
        '--card-content-radius': `${borderRadius - borderSize}px`,
        '--pseudo-element-background-image': `linear-gradient(0deg, ${neonColors.firstColor}, ${neonColors.secondColor})`,
        '--pseudo-element-width': `${dimensions.width + borderSize * 2}px`,
        '--pseudo-element-height': `${dimensions.height + borderSize * 2}px`,
        '--after-blur': `${dimensions.width / 3}px`,
      } as CSSProperties
    "
    :class="neonGradientCardStyles().wrapper({ class: props.class })"
  >
    <div :class="neonGradientCardStyles().inner()">
      <slot />
    </div>
  </div>
</template>

<script lang="ts">
  import type { PrimitiveProps } from "reka-ui";
  import type { CSSProperties, HTMLAttributes } from "vue";

  export interface NeonColorsProps {
    firstColor: string;
    secondColor: string;
  }

  export interface NeonGradientCardProps extends PrimitiveProps {
    /**
     * @default ""
     * @type string
     * @description
     * The className of the card
     */
    class?: HTMLAttributes["class"];

    /**
     * @default 5
     * @type number
     * @description
     * The size of the border in pixels
     * */
    borderSize?: number;

    /**
     * @default 20
     * @type number
     * @description
     * The size of the radius in pixels
     * */
    borderRadius?: number;

    /**
     * @default "{ firstColor: '#ff00aa', secondColor: '#00FFF1' }"
     * @type string
     * @description
     * The colors of the neon gradient
     * */
    neonColors?: NeonColorsProps;

    [key: string]: any;
  }

  export const neonGradientCardStyles = tv({
    slots: {
      wrapper: "relative z-10 size-full rounded-[var(--border-radius)]",
      inner: [
        "relative size-full min-h-[inherit] rounded-[var(--card-content-radius)] bg-gray-100 p-6",
        "before:absolute before:-top-[var(--border-size)] before:-left-[var(--border-size)] before:-z-10 before:block",
        "before:h-[var(--pseudo-element-height)] before:w-[var(--pseudo-element-width)] before:rounded-[var(--border-radius)] before:content-['']",
        "before:bg-[linear-gradient(0deg,var(--neon-first-color),var(--neon-second-color))] before:bg-[length:100%_200%]",
        "before:animate-background-position-spin",
        "after:absolute after:-top-[var(--border-size)] after:-left-[var(--border-size)] after:-z-10 after:block",
        "after:h-[var(--pseudo-element-height)] after:w-[var(--pseudo-element-width)] after:rounded-[var(--border-radius)] after:blur-[var(--after-blur)] after:content-['']",
        "after:bg-[linear-gradient(0deg,var(--neon-first-color),var(--neon-second-color))] after:bg-[length:100%_200%] after:opacity-80",
        "after:animate-background-position-spin",
        "dark:bg-neutral-900",
      ],
    },
  });
</script>

<script lang="ts" setup>
  const props = withDefaults(defineProps<NeonGradientCardProps>(), {
    borderSize: 2,
    borderRadius: 20,
    neonColors: () => ({
      firstColor: "#ff00aa",
      secondColor: "#00FFF1",
    }),
  });

  const containerRef = useTemplateRef("containerRef");
  const dimensions = ref({ width: 0, height: 0 });

  const updateDimensions = () => {
    if (containerRef.value) {
      const { offsetWidth, offsetHeight } = containerRef.value;
      dimensions.value = { width: offsetWidth, height: offsetHeight };
    }
  };

  useEventListener("resize", updateDimensions);

  onMounted(() => {
    updateDimensions();
  });
  watchEffect(updateDimensions);
</script>

Add animation to tailwind.css file

@theme inline {
  --animate-background-position-spin: background-position-spin 3000ms infinite alternate;

  @keyframes background-position-spin {
    0% {
      background-position: top center;
    }
    100% {
      background-position: bottom center;
    }
  }
}

Usage

Neon Gradient Card