
Rotating Rainbow Gradient Glowing Border Button
Published
15 copy-paste CSS glowing border buttons — a rotating conic-gradient ring, a hover-triggered neon pulse, an animated border trace that laps the perimeter via mask-composite, a cyberpunk clipped-corner glow drawn with drop-shadow(), a dark-mode form input with :user-valid / :user-invalid state colors, a Tailwind-style blur glow recipe, a frosted glassmorphism border, a minimalist e-commerce outer glow, a breathing pulse urgency CTA, an inner-glow pressed toggle with aria-pressed, a subtle dark-theme glow, an SVG stroke-trace, a synthwave dual neon, a hue-shifting gradient border with @property, and a WCAG-ready accessible focus glow with a real :focus-visible outline. Scoped under .gbb-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.
Related51 CSS Buttons20 CSS Animated Buttons36 CSS Button Hover Effects

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published
box-shadow halos, which is what Demo 02 (Neon Pulse) leans on — a tight 0 0 8px inner ring plus a 0 0 24px outer bloom, both in the accent color, both animated via a keyframe that scales the blur radius. box-shadow is composited on the GPU, doesn't force layout, and respects border-radius, which is why it stays the workhorse for neon and pulse effects. The second is filter: drop-shadow(), used in Demo 04 (Cyberpunk Clipped-Corner). Unlike box-shadow, drop-shadow follows the actual painted shape — including anything cut by clip-path — which is the only way to get a real glow around a hexagonal or bracketed HUD button. The tradeoff is cost: filter triggers an offscreen buffer, so use it on a wrapper element, not on a hover-repeating keyframe. The third is a mask-composite ring, the technique in Demo 03 (Comet Border Trace). You paint a conic-gradient on a ::before, then use a two-layer mask with mask-composite: exclude to punch out the interior — leaving just a 1-2px ring that the gradient rotates through. The fourth is @property-driven conic-gradient rotation, the technique in Demo 01 (Rotating Rainbow Ring). Without @property registering the angle as an <angle> type, the browser interpolates it as a string and snaps rather than tweening. For picking between them: use box-shadow for soft halos, drop-shadow when clip-path is involved, mask-composite for hairline animated borders, and @property + conic for anything that needs to rotate continuously.<angle>: @property --angle { syntax: '<angle>'; initial-value: 0deg; inherits: false; }. Second, paint the border ring on a ::before pseudo-element positioned inset: -2px behind the button, filled with background: conic-gradient(from var(--angle), #ff006e, #8338ec, #3a86ff, #ff006e) — the repeated first color is what closes the loop cleanly. Third, animate the angle: @keyframes spin { to { --angle: 360deg; } } plus animation: spin 4s linear infinite. The gotcha every developer hits: without @property, the CSS parses fine, DevTools shows the keyframe running, and nothing rotates. This is because unregistered custom properties are treated as * (any string) by the interpolation engine — 0deg to 360deg gets discretely swapped, not tweened, so the ring appears frozen. @property types the variable so the browser knows to interpolate it numerically. Browser support matters: @property shipped in Chromium 85+, Safari 16.4+, Firefox 128+. For older Firefox, you can fall back to a static gradient by putting the animation inside @supports (background: paint(something)) or simply accepting the static ring — it still reads as a glowing border. To layer this with a solid button interior, give the actual <button> a background matching your surface color and use z-index to stack it over the ::before. For the reduced-motion path, wrap the animation in @media (prefers-reduced-motion: no-preference) so the ring stays visible but stationary.::before pseudo-element sits inset: 0 over the button, painted with a conic-gradient(from var(--angle), transparent 0deg, transparent 300deg, #00ffcc 340deg, transparent 360deg). That gives you a short bright arc in an otherwise transparent circle — the comet's tail. On its own, this fills the entire button surface. To turn it into just a ring, you apply two mask layers: mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0) with mask-composite: exclude. The first mask covers the interior via content-box; the second covers everything; XOR-ing them leaves only the padding-box ring visible. Set padding: 2px on the pseudo to control ring thickness. The -webkit-mask-composite quirk: Safari's prefixed syntax uses different keywords. Ship both: -webkit-mask-composite: xor; mask-composite: exclude; — those two produce the same visual result across engines but the keywords are not interchangeable. Animate the angle with the same @property pattern from Demo 01 so the comet loops around. For the hover-only variant, put the animation behind button:hover::before { animation: trace 2s linear infinite; } and give the base state opacity: 0 with a short transition so the ring fades in when the cursor arrives. One accessibility note: pair the trace with a color change or shadow on :focus-visible — keyboard users don't hover, so a hover-only trace leaves them without any focus affordance.outline: 2px solid var(--focus) or an equivalent box-shadow: 0 0 0 2px var(--focus) ring. Rule two: use :focus-visible, not :focus. :focus fires on mouse clicks too, which is why designers historically stripped it and broke keyboard nav. :focus-visible only fires when the browser's heuristic decides focus should be shown — keyboard tabs, screen reader nav — so you can style it aggressively without lighting up on click. Rule three: never gate the glow on :hover alone. Keyboard users don't hover. Every hover-triggered visual should also be triggered by :focus-visible: button:hover, button:focus-visible { box-shadow: 0 0 24px var(--glow); }. Rule four: support forced-colors (Windows High Contrast Mode). Inside @media (forced-colors: active), replace glow colors with system tokens: outline: 2px solid CanvasText; box-shadow: none;. The forced-colors engine strips box-shadow and custom colors anyway, so if glow is your only affordance, high-contrast users see nothing. Also honor prefers-reduced-motion — replace any pulsing keyframe with a static glow at the same intensity.box-shadow: @keyframes breathe { 0%, 100% { box-shadow: 0 0 20px rgba(99, 102, 241, 0.4); } 50% { box-shadow: 0 0 40px rgba(99, 102, 241, 0.7); } } applied as animation: breathe 3s ease-in-out infinite. Now the real gotcha, which is why this FAQ exists. On hover, you want the button to snap to a stronger static glow — a reward state. Every developer's first instinct is button:hover { animation-play-state: paused; }. This fails silently. A paused animation still owns the box-shadow property — it's frozen at whatever keyframe frame the pause caught, and any box-shadow you set in :hover is overridden by the animation's computed value. The fix: button:hover { animation: none; box-shadow: 0 0 48px rgba(99, 102, 241, 0.9); }. Setting animation: none fully removes the declaration from the cascade, freeing box-shadow for the hover rule to write. This is a real, tested pattern — every SaaS pricing team hits it. The other pattern detail: only apply breathing to ONE button per pricing card grid. Multiple pulsing CTAs read as chaos and undermine the visual hierarchy that says 'pick this tier'. Also honor prefers-reduced-motion by replacing the animation with a static strong glow — vestibular-sensitive users still see the affordance, just not the pulse. For the copy on the button, keep it verb-first (Start free trial, Get started, Upgrade to Pro) — the glow does the attention work, so the text can be direct.:user-valid and :user-invalid. Old-school :valid and :invalid match from page load — meaning an empty required field is red before the user has typed a single character. Every user hates this. :user-valid and :user-invalid only match AFTER the user has interacted with the field and blurred or submitted, which is the actual UX contract every serious signup form wants. This is what Stripe Elements, Plaid Link, and Linear's signup flow ship. The CSS pattern for the input itself: neutral box-shadow: 0 0 0 1px var(--border) at rest, box-shadow: 0 0 0 2px var(--accent), 0 0 12px color-mix(in oklch, var(--accent) 40%, transparent) on :focus-visible. On :user-valid, swap the accent for a green token; on :user-invalid, swap for red. Using color-mix() for the glow layer means you define one accent color and derive the transparent bloom from it — no separate glow variable to maintain. The caret-color: var(--accent) declaration matches the blinking cursor to the state color, which is the polish detail that reads as built by someone who cares. The accessibility non-negotiable: never remove outline without replacing it with a box-shadow ring at 3:1 contrast against the input background. Screen reader users navigating with keyboard need to know which field has focus — a color-only cue fails colorblind users too, so the ring thickness itself is the affordance. For the submit button, tie its enabled state to form:has(:user-invalid) button[type='submit'] { opacity: 0.5; pointer-events: none; } — modern :has() support means you can gate submission on form validity without a single line of JavaScript.clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px) — a rectangle with two corners cut, giving that HUD/lock-on silhouette. The critical gotcha: clip-path clips everything painted on the element, including box-shadow. If you put the glow on the same element as the clip-path, the glow gets cut off at the polygon edge and you see nothing outside the shape. The fix: wrap the button in a container div, apply clip-path to the button, and apply filter: drop-shadow(0 0 16px #00ffcc) to the WRAPPER. drop-shadow follows the clipped shape's actual painted geometry, so the cyan bloom hugs the hex outline instead of a rectangular bounding box. The corner-bracket HUD detail — those small L-shaped marks in the corners — comes from two absolutely-positioned pseudo-elements: ::before in the top-left with border: 2px 0 0 2px solid #00ffcc; width: 12px; height: 12px; and ::after mirrored in the bottom-right. That's what sells the targeting reticle aesthetic that gaming and Web3 audiences read as native. On hover, animate the bracket size outward with a transform: scale(1.15) plus a stronger drop-shadow — the brackets read as tracking to the click. Color-wise, stick to the cyan/magenta/lime palette that dominates crypto UI — #00ffcc, #ff00aa, #c3ff00 — because those are the exact tokens users associate with on-chain action. For accessibility, pair the visual with :focus-visible { outline: 2px solid currentColor; outline-offset: 4px; } so keyboard users get a real ring even inside the clipped shape.position: relative and add a ::before with content: ''; position: absolute; inset: -2px; background: linear-gradient(90deg, #ff006e, #8338ec, #3a86ff); filter: blur(12px); opacity: 0.6; z-index: -1; transition: opacity 0.3s, transform 0.3s; then on hover opacity: 1; transform: scale(1.1);. The Tailwind class equivalent: wrap the button in a <div class='relative group'> and add a sibling <div class='absolute -inset-0.5 bg-gradient-to-r from-pink-500 via-purple-500 to-blue-500 rounded-lg blur opacity-60 group-hover:opacity-100 group-hover:scale-110 transition duration-300'></div> before the actual button. The group and group-hover: primitives are how Tailwind wires parent-triggered child states without extra JS. Why blur-underneath instead of a real border image: gradient borders in CSS are historically painful — border-image with gradients doesn't respect border-radius in most engines. Blurring a shape behind the button sidesteps the whole problem and produces the softer, more expensive-looking glow that ships on production SaaS sites. This pattern works identically in Next.js (App Router or Pages), Astro (both static and island hydration), SvelteKit, Nuxt, and Remix — nothing about it is framework-specific, because it's just two divs and a hover state. For the color palette, sample from your brand's existing gradient tokens rather than defaulting to purple-pink — the effect looks generic if every button uses the Tailwind rainbow.<button type='button'>, real <a href>, real <input>) plus scoped CSS under a .gbb-NN class prefix, plus (in exactly one case, Demo 10's aria-pressed toggle) a small vanilla JS listener. Porting is mechanical. For React and Next.js: the HTML pastes into JSX with the usual attribute renames — class becomes className, inline styles become objects, for becomes htmlFor. Keep type='button' on every non-submit button so React doesn't accidentally submit an ancestor form. Drop the CSS into a CSS Module, a <style jsx> block, or a global stylesheet — the .gbb-NN scoping means it won't leak. For Demo 10's toggle JS, wrap it in a useEffect(() => { ... }, []) so the listener attaches after mount and gets torn down on unmount. For Vue 3 (Composition or Options): HTML goes straight into the template unchanged, CSS into a <style scoped> block, JS into onMounted(). For Svelte and SvelteKit: HTML unchanged, CSS in the component's <style> block (already scoped by default), JS in onMount(). For Astro: HTML into an .astro file exactly as-is, CSS in the frontmatter <style> block (scoped by default), JS in a <script> tag (deferred by default, which is what you want). For Tailwind users who want to convert: the pure-CSS versions are provided as the ground truth so you can translate class-by-class, and Demo 06 is already written with the Tailwind blur-underneath pattern as its explicit source. The one universal note: 14 of the 15 demos are pure CSS with zero runtime cost — no hydration, no reactivity, no event listeners. They render server-side in Next.js and Astro static output identically to how they render in the browser. Only Demo 10 needs the JS boundary, and its handler is 8 lines.15 mouse-aware 3D tilt hover cards — e-commerce product spotlight, glassmorphism parallax team card, interactive pricing tier, holographic NFT collectible, pop-out mascot, dark tech grid with border glow, media player album art, blog article preview, cyberpunk neon glow, dashboard KPI widget, minimalist real estate, flip-to-back tilt, mobile app showcase, course learning card, and a Pure CSS touch-friendly tilt. Vanilla JS writes --rx/--ry/--mx/--my; all rendering stays in CSS on the GPU.
24 hand-coded CSS animated card patterns organised by what triggers the motion, not by card style. Scroll and entrance triggers: staggered grid reveals, @starting-style mount-in, scroll-driven scale and fade on animation-timeline: view(), View Transitions API card-to-detail morphs, blur-to-sharp lazy image loading, and FLIP re-layout when a filter changes. State-change triggers: add-to-cart success morph, like and save particle burst, expand and collapse to intrinsic height with calc-size(), swipe-to-dismiss on Pointer Events, and skeleton-to-loaded crossfade. Data-driven triggers: count-up KPI metrics, SVG sparkline draw-on-enter, and progress ring fill on load. Ambient idle loops: breathing glow, floating drift, animated gradient mesh backgrounds, conic rotating borders, auto-cycling testimonial decks, and live status pulses. Plus four hover patterns where the effect is the topic itself: cursor spotlight, holographic foil glare, corner ribbon slide, and horizontal accordion expand. Every card is scoped under a .ac-NN prefix for no-collision pasting, guards prefers-reduced-motion, animates compositor-only properties, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.
22 hand-coded CSS avatars for chat apps, team dashboards, comment threads, account menus, and social profiles. Covers circular and squircle shapes, gradient and conic story rings, hexagon clip-path crops, online status dots, notification count badges, verified checkmarks, stacked facepiles with plus-N overflow, initials fallbacks for users without photos, broken-image recovery, avatar pickers, and a size scale system where the ring, badge, and status dot all scale from a single custom property.