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-border-draw.html7 Desc : Button whose border draws itself around the label on hover8 ============================================================9-->10<!--svgarden11title: Border-draw button12slug: btn-border-draw13category: buttons14tags: [button, border, draw, hover]15difficulty: intermediate16techniques: [pathLength, stroke-dashoffset, transition-reversal, letter-spacing]17how_it_works: >18 A rect stretched over the button (preserveAspectRatio="none" + non-scaling19 stroke) is normalized to pathLength="100", so one dash of 100 hides the whole20 border. Hovering transitions stroke-dashoffset to 0 and the outline traces21 itself around the perimeter; because this is a transition rather than a22 keyframe animation, leaving the button plays the exact same motion backwards23 for free. A faint static track rect keeps the affordance visible at rest, and24 a small letter-spacing transition on the label makes the whole gesture feel25 intentional rather than decorative.26customizable:27 - { var: "--sg-color", label: "Border", type: color, default: "#7F77DD" }28 - { var: "--sg-size", label: "Font size", type: range, min: 12, max: 26, default: 16, unit: px }29 - { var: "--sg-duration", label: "Speed", type: range, min: 0.2, max: 2, step: 0.1, default: 0.6, unit: s }30created: 2026-08-1031-->32<button class="sg-btn-border-draw" type="button" style="--sg-color:#7F77DD; --sg-size:16px; --sg-duration:0.6s;">33 <svg viewBox="0 0 100 40" preserveAspectRatio="none" aria-hidden="true" focusable="false">34 <rect class="sg-btn-border-draw-track" x="1.5" y="1.5" width="97" height="37" rx="9" pathLength="100" />35 <rect class="sg-btn-border-draw-ink" x="1.5" y="1.5" width="97" height="37" rx="9" pathLength="100" />36 </svg>37 <span class="sg-btn-border-draw-label">Draw me in</span>38</button>39<style>40 .sg-btn-border-draw {41 position: relative;42 appearance: none;43 border: 0;44 background: transparent;45 color: inherit;46 font: inherit;47 font-size: var(--sg-size);48 font-weight: 600;49 padding: 0.75em 1.6em;50 cursor: pointer;51 }52 .sg-btn-border-draw svg {53 position: absolute;54 inset: 0;55 width: 100%;56 height: 100%;57 pointer-events: none;58 }59 .sg-btn-border-draw rect {60 /* no non-scaling-stroke here: Chromium computes dash patterns in screen61 space with it, which breaks the pathLength=100 normalization */62 fill: none;63 stroke: var(--sg-color);64 stroke-width: 2;65 stroke-linecap: round;66 }67 .sg-btn-border-draw-track {68 opacity: 0.25;69 }70 .sg-btn-border-draw-ink {71 /* gap 102 > pathLength so the round end-caps never bleed onto the path at rest */72 stroke-dasharray: 100 102;73 stroke-dashoffset: 101;74 transition: stroke-dashoffset var(--sg-duration) ease-in-out;75 }76 .sg-btn-border-draw-label {77 letter-spacing: 0.02em;78 transition: letter-spacing var(--sg-duration) ease;79 }80 .sg-btn-border-draw:hover .sg-btn-border-draw-ink,81 .sg-btn-border-draw:focus-visible .sg-btn-border-draw-ink {82 stroke-dashoffset: 0;83 }84 .sg-btn-border-draw:hover .sg-btn-border-draw-label,85 .sg-btn-border-draw:focus-visible .sg-btn-border-draw-label {86 letter-spacing: 0.09em;87 }88</style>89