Neon trace button
Neon light dash orbiting the button border with a hue sweep
⚠ Browser support @property is required for the smooth hue sweep (Chromium, Safari 16.4+, Firefox 128+); older browsers show the orbiting dash at a fixed hue.
Techniques: @property · stroke-dasharray-gap · hsl-calc · drop-shadow
<!--
============================================================
SVGarden — https://www.svgarden.dev
Author : Simon-Pierre Boucher
Contact: contact@spboucher.ai
File : snippets/buttons/btn-neon-trace.html
Desc : Neon light dash orbiting the button border with a hue sweep
============================================================
-->
<button class="sg-btn-neon-trace" type="button" style="--sg-neon-hue:255; --sg-size:16px; --sg-duration:2.2s;">
<svg viewBox="0 0 100 40" preserveAspectRatio="none" aria-hidden="true" focusable="false">
<rect class="sg-btn-neon-trace-track" x="1.5" y="1.5" width="97" height="37" rx="9" pathLength="100" />
<rect class="sg-btn-neon-trace-spark" x="1.5" y="1.5" width="97" height="37" rx="9" pathLength="100" />
</svg>
<span class="sg-btn-neon-trace-label">Neon trace</span>
</button>
<style>
@property --sg-btn-neon-trace-cycle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.sg-btn-neon-trace {
--sg-btn-neon-trace-cycle: 0deg;
position: relative;
appearance: none;
border: 0;
background: transparent;
font: inherit;
font-size: var(--sg-size);
font-weight: 600;
color: inherit;
padding: 0.8em 1.9em;
cursor: pointer;
animation: sg-btn-neon-trace-hue calc(var(--sg-duration) * 3) linear infinite;
}
.sg-btn-neon-trace svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: visible;
}
.sg-btn-neon-trace rect {
/* no non-scaling-stroke: it makes Chromium measure dashes in screen space,
desynchronizing the pathLength-based orbit */
fill: none;
stroke-width: 2;
stroke-linecap: round;
}
.sg-btn-neon-trace-track {
stroke: rgba(128, 128, 128, 0.35);
}
.sg-btn-neon-trace-spark {
stroke: hsl(calc(var(--sg-neon-hue) * 1deg + var(--sg-btn-neon-trace-cycle)) 90% 62%);
stroke-dasharray: 14 86;
stroke-dashoffset: 0;
filter: drop-shadow(0 0 4px hsl(calc(var(--sg-neon-hue) * 1deg + var(--sg-btn-neon-trace-cycle)) 90% 62%));
animation: sg-btn-neon-trace-orbit var(--sg-duration) linear infinite;
}
.sg-btn-neon-trace:hover .sg-btn-neon-trace-spark,
.sg-btn-neon-trace:focus-visible .sg-btn-neon-trace-spark {
stroke-dasharray: 30 70;
}
@keyframes sg-btn-neon-trace-orbit {
to { stroke-dashoffset: -100; }
}
@keyframes sg-btn-neon-trace-hue {
from { --sg-btn-neon-trace-cycle: 0deg; }
to { --sg-btn-neon-trace-cycle: 360deg; }
}
</style>