Rainbow Border Button with Underglow

A solid button with a strip of rainbow colour running along its lower edge and a soft glow of the same colours spilling out beneath it. The colours slide sideways in a loop and the glow widens on hover. The demo shows a dark version on a white panel and a white version on a black panel, each in large, medium and small. On phones the two panels stack.

Code

// Adapted from Magic UI (https://magicui.design/docs/components/rainbow-button)
// Copyright (c) Magic UI. MIT License: https://we.inc/components/licenses
import type { ButtonHTMLAttributes, ReactNode } from "react";
import { motion, useReducedMotion } from "framer-motion";

const sizes = {
  sm: "h-10 rounded-xl px-4 text-xs",
  md: "h-11 rounded-xl px-6 text-sm",
  lg: "h-14 rounded-2xl px-9 text-base",
};

interface RainbowButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  children: ReactNode;
  /** "dark" is a near-black button, "light" is a white one. Both get the moving rainbow edge. */
  tone?: "dark" | "light";
  size?: keyof typeof sizes;
}

function RainbowButton({ children, tone = "dark", size = "md", className = "", ...props }: RainbowButtonProps) {
  return (
    <button
      type="button"
      className={`rainbow-border-button-with-underglow rainbow-border-button-with-underglow-${tone} relative inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap font-semibold transition-transform duration-200 hover:-translate-y-0.5 active:translate-y-0 active:scale-[0.98] focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 ${tone === "dark" ? "text-white focus-visible:ring-offset-white" : "text-neutral-900 focus-visible:ring-offset-neutral-950"} ${sizes[size]} ${className}`}
      {...props}
    >
      {children}
    </button>
  );
}

function Arrow() {
  return (
    <svg viewBox="0 0 20 20" className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M4 10h12m0 0l-5-5m5 5l-5 5" />
    </svg>
  );
}

export default function RainbowBorderButtonWithUnderglow() {
  const reduce = useReducedMotion();
  const enter = (delay: number) => ({
    initial: reduce ? false : { opacity: 0, y: 20 },
    animate: { opacity: 1, y: 0 },
    transition: { type: "spring" as const, stiffness: 130, damping: 20, delay },
  });

  return (
    <section className="bg-neutral-100 px-4 py-14 sm:px-6 sm:py-20">
      <style>{`
        @keyframes rainbow-border-button-with-underglow-slide {
          from { background-position: 0% 0; }
          to { background-position: 200% 0; }
        }
        .rainbow-border-button-with-underglow {
          --rainbow: linear-gradient(90deg, #f43f5e, #a3e635, #3b82f6, #22d3ee, #a855f7, #f43f5e);
          border: 2px solid transparent;
          background-size: 200% 100%;
          background-clip: padding-box, border-box, border-box;
          background-origin: border-box;
          animation: rainbow-border-button-with-underglow-slide 2.4s linear infinite;
        }
        .rainbow-border-button-with-underglow-dark {
          background-image: linear-gradient(#121213, #121213), linear-gradient(#121213 50%, rgba(18, 18, 19, 0.6) 80%, rgba(18, 18, 19, 0)), var(--rainbow);
        }
        .rainbow-border-button-with-underglow-light {
          background-image: linear-gradient(#fff, #fff), linear-gradient(#fff 50%, rgba(255, 255, 255, 0.6) 80%, rgba(255, 255, 255, 0)), var(--rainbow);
        }
        .rainbow-border-button-with-underglow::before {
          content: "";
          position: absolute;
          bottom: -22%;
          left: 50%;
          z-index: -1;
          height: 24%;
          width: 62%;
          transform: translateX(-50%);
          background: var(--rainbow);
          background-size: 200% 100%;
          filter: blur(14px);
          animation: rainbow-border-button-with-underglow-slide 2.4s linear infinite;
          transition: width 0.3s ease, filter 0.3s ease;
        }
        .rainbow-border-button-with-underglow:hover::before { width: 82%; filter: blur(18px); }
        @media (prefers-reduced-motion: reduce) {
          .rainbow-border-button-with-underglow,
          .rainbow-border-button-with-underglow::before { animation: none; }
        }
      `}</style>

      <div className="mx-auto grid max-w-5xl overflow-hidden rounded-[32px] shadow-xl shadow-neutral-900/10 ring-1 ring-neutral-900/10 md:grid-cols-2">
        <motion.div {...enter(0)} className="relative isolate bg-white px-6 py-14 text-center sm:px-10 sm:py-16">
          <p className="text-sm font-semibold uppercase tracking-widest text-neutral-600">Prism Notes 3.0</p>
          <h2 className="mt-3 text-3xl font-semibold tracking-tight text-neutral-900 sm:text-4xl">Colour-code a whole notebook in one tap</h2>
          <p className="mx-auto mt-4 max-w-sm text-neutral-600">Tags, highlights and page tabs now share one palette that follows you across devices.</p>
          <div className="mt-9">
            <RainbowButton tone="dark" size="lg">
              Get the update <Arrow />
            </RainbowButton>
          </div>
          <div className="mt-9 flex flex-wrap items-center justify-center gap-4">
            <RainbowButton tone="dark" size="md">Read the notes</RainbowButton>
            <RainbowButton tone="dark" size="sm">Share</RainbowButton>
          </div>
        </motion.div>

        <motion.div {...enter(0.1)} className="relative isolate bg-neutral-950 px-6 py-14 text-center sm:px-10 sm:py-16">
          <p className="text-sm font-semibold uppercase tracking-widest text-neutral-400">For teams</p>
          <h2 className="mt-3 text-3xl font-semibold tracking-tight text-white sm:text-4xl">Shared palettes for every workspace</h2>
          <p className="mx-auto mt-4 max-w-sm text-neutral-300">Set the colours once and every notebook in the team picks them up. $6 per person a month.</p>
          <div className="mt-9">
            <RainbowButton tone="light" size="lg">
              Start a team trial <Arrow />
            </RainbowButton>
          </div>
          <div className="mt-9 flex flex-wrap items-center justify-center gap-4">
            <RainbowButton tone="light" size="md">Compare plans</RainbowButton>
            <RainbowButton tone="light" size="sm">Invite</RainbowButton>
          </div>
        </motion.div>
      </div>
    </section>
  );
}

Install with one command: npx shadcn@latest add https://we.inc/r/rainbow-border-button-with-underglow.json

When to use it

How to customize it

Accessibility

Questions

How is the rainbow edge drawn?

The button has a transparent border and three stacked backgrounds. The top one is the solid face, clipped to the padding box. The second fades from the face colour to clear and is clipped to the border box, which hides the rainbow on the top and upper sides. The third is the rainbow itself. Only where the second layer is clear does the colour show, which is the lower edge.

What makes the colours move?

The rainbow gradient is twice the width of the button and a keyframe animation slides its background position by that full width. As the gradient starts and ends on the same colour, the loop is seamless.

Why does the glow need `isolate` on the parent?

The glow is a pseudo-element with a negative z-index so it sits behind the button face. Without a stacking context on a parent it would slip behind the section background and vanish. The demo panels use the `isolate` class for this.

Can I make the whole border rainbow, not just the bottom?

Yes. Remove the middle gradient layer from the two `background-image` rules and change `background-clip` to `padding-box, border-box`.

Can it be a link?

Yes. Put the same classes on an `a` element. The effect is all in the CSS classes and does not depend on the element type.

Adapted from Rainbow Button in Magic UI, used under its open-source licence. Licences and credits

Related: Button with Rotating Gradient Glow Border, Shimmer Button with Light Sweep in Three Sizes, Click Ripple Buttons with Ink Spread.

More animated buttons or all free UI components. Start for free to have a full site built around it.

More in Animated Buttons

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap