spb/svgarden Public
SVGarden — searchable bank of 74 self-contained SVG+CSS animation snippets (svgarden.dev)
HTML 79.2%
Astro 10.3%
JavaScript 6%
CSS 3.7%
Shell 0.8%
1<!--2 ============================================================3 SVGarden — https://www.svgarden.dev4 Author : Simon-Pierre Boucher5 Contact: contact@spboucher.ai6 File : snippets/buttons/btn-neon-trace.html7 Desc : Neon light dash orbiting the button border with a hue sweep8 ============================================================9-->10<!--svgarden11title: Neon trace button12slug: btn-neon-trace13category: buttons14tags: [button, neon, dasharray, at-property]15difficulty: advanced16techniques: ["@property", stroke-dasharray-gap, hsl-calc, drop-shadow]17how_it_works: >18 A short dash (stroke-dasharray "14 86" on a pathLength-100 rect) endlessly19 slides its dashoffset, so a single bright segment orbits the border. Its color20 is hsl(calc(var(--sg-neon-hue) * 1deg + var(--sg-btn-neon-trace-cycle)) …),21 where the cycle variable is registered with @property as a typed angle — that22 registration is what lets the keyframe animation interpolate it smoothly23 instead of snapping. Browsers without @property still animate the custom24 property, but discretely: the cycle runs 0deg→360deg, and since those are the25 same hue the snap is literally invisible — a degradation path chosen so the26 fallback needs no @supports at all.27support: "@property is required for the smooth hue sweep (Chromium, Safari 16.4+, Firefox 128+); older browsers show the orbiting dash at a fixed hue."28customizable:29 - { var: "--sg-neon-hue", label: "Base hue", type: range, min: 0, max: 360, default: 255 }30 - { var: "--sg-size", label: "Font size", type: range, min: 12, max: 26, default: 16, unit: px }31 - { var: "--sg-duration", label: "Orbit time", type: range, min: 0.8, max: 6, step: 0.1, default: 2.2, unit: s }32created: 2026-08-1033-->34<button class="sg-btn-neon-trace" type="button" style="--sg-neon-hue:255; --sg-size:16px; --sg-duration:2.2s;">35 <svg viewBox="0 0 100 40" preserveAspectRatio="none" aria-hidden="true" focusable="false">36 <rect class="sg-btn-neon-trace-track" x="1.5" y="1.5" width="97" height="37" rx="9" pathLength="100" />37 <rect class="sg-btn-neon-trace-spark" x="1.5" y="1.5" width="97" height="37" rx="9" pathLength="100" />38 </svg>39 <span class="sg-btn-neon-trace-label">Neon trace</span>40</button>41<style>42 @property --sg-btn-neon-trace-cycle {43 syntax: '<angle>';44 inherits: false;45 initial-value: 0deg;46 }47 .sg-btn-neon-trace {48 --sg-btn-neon-trace-cycle: 0deg;49 position: relative;50 appearance: none;51 border: 0;52 background: transparent;53 font: inherit;54 font-size: var(--sg-size);55 font-weight: 600;56 color: inherit;57 padding: 0.8em 1.9em;58 cursor: pointer;59 animation: sg-btn-neon-trace-hue calc(var(--sg-duration) * 3) linear infinite;60 }61 .sg-btn-neon-trace svg {62 position: absolute;63 inset: 0;64 width: 100%;65 height: 100%;66 pointer-events: none;67 overflow: visible;68 }69 .sg-btn-neon-trace rect {70 /* no non-scaling-stroke: it makes Chromium measure dashes in screen space,71 desynchronizing the pathLength-based orbit */72 fill: none;73 stroke-width: 2;74 stroke-linecap: round;75 }76 .sg-btn-neon-trace-track {77 stroke: rgba(128, 128, 128, 0.35);78 }79 .sg-btn-neon-trace-spark {80 stroke: hsl(calc(var(--sg-neon-hue) * 1deg + var(--sg-btn-neon-trace-cycle)) 90% 62%);81 stroke-dasharray: 14 86;82 stroke-dashoffset: 0;83 filter: drop-shadow(0 0 4px hsl(calc(var(--sg-neon-hue) * 1deg + var(--sg-btn-neon-trace-cycle)) 90% 62%));84 animation: sg-btn-neon-trace-orbit var(--sg-duration) linear infinite;85 }86 .sg-btn-neon-trace:hover .sg-btn-neon-trace-spark,87 .sg-btn-neon-trace:focus-visible .sg-btn-neon-trace-spark {88 stroke-dasharray: 30 70;89 }90 @keyframes sg-btn-neon-trace-orbit {91 to { stroke-dashoffset: -100; }92 }93 @keyframes sg-btn-neon-trace-hue {94 from { --sg-btn-neon-trace-cycle: 0deg; }95 to { --sg-btn-neon-trace-cycle: 360deg; }96 }97</style>98