SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/svg-stroke-buttons

7 CSS SVG Stroke Buttons

6 Pure CSS1 with JSPublished 2026-10-05Free · no sign-up

Seven buttons whose outlines and icons are SVG strokes animated with CSS: a border that draws itself on hover, a dash that chases round the edge, corner brackets that close into a frame, an arrow and a tick that draw in, a gradient outline with glow and a link with a hand-drawn underline.

01. Border That Draws Itself

SVGHover

An SVG rectangle sits over the button and its stroke is hidden with stroke-dasharray and stroke-dashoffset. On hover or keyboard focus the offset animates to zero, so the outline draws around the button in one continuous line. pathLength="100" keeps the dash maths simple at any size.

View code
<button class="ssb1"><svg viewBox="0 0 200 56" aria-hidden="true"><rect x="1" y="1" width="198" height="54" rx="10" pathLength="100"/></svg>Get started</button>
.ssb1 { box-sizing: border-box; position: relative; width: 200px; max-width: 100%; height: 56px; border: 1px solid #2a3350; border-radius: 10px; background: #121828; color: #e8ecff; font: 600 16px Inter, system-ui, sans-serif; cursor: pointer; }
.ssb1 svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.ssb1 rect { fill: none; stroke: #7c9cff; stroke-width: 2; stroke-dasharray: 100; stroke-dashoffset: 100; transition: stroke-dashoffset .7s ease; }
.ssb1:hover rect, .ssb1:focus-visible rect { stroke-dashoffset: 0; }
.ssb1:focus-visible { outline: 2px solid #7c9cff; outline-offset: 4px; }
@media (prefers-reduced-motion: reduce) { .ssb1 rect { transition: none; } }

02. Chasing Stroke Segment

SVGAnimation

A short bright dash travels round the border on a loop, made by a dash pattern of 18 visible units and 82 gaps whose offset is animated. It suits one important call to action. The animation stops for visitors who prefer reduced motion, leaving a static highlight.

View code
<button class="ssb2"><svg viewBox="0 0 220 56" aria-hidden="true"><rect class="base" x="1" y="1" width="218" height="54" rx="27"/><rect class="run" x="1" y="1" width="218" height="54" rx="27" pathLength="100"/></svg>Join the beta</button>
.ssb2 { position: relative; width: 220px; max-width: 100%; height: 56px; border: 0; border-radius: 28px; background: #0f1424; color: #f2f4ff; font: 700 16px Inter, system-ui, sans-serif; cursor: pointer; }
.ssb2 svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.ssb2 rect { fill: none; stroke-width: 2; }
.ssb2 .base { stroke: #2b3354; }
.ssb2 .run { stroke: #22d3ee; stroke-linecap: round; stroke-dasharray: 18 82; animation: ssb2 2.4s linear infinite; filter: drop-shadow(0 0 4px #22d3ee); }
@keyframes ssb2 { to { stroke-dashoffset: -100; } }
.ssb2:focus-visible { outline: 2px solid #22d3ee; outline-offset: 4px; }
@media (prefers-reduced-motion: reduce) { .ssb2 .run { animation: none; } }

03. Corner Brackets to Full Frame

SVGHover

At rest only the four corners of the frame show, like camera brackets. The stroke-dasharray is a list of nine values worked out from the real side lengths so each dash sits on a corner. On hover every dash grows and every gap shrinks to zero, closing the frame evenly from all four corners.

View code
<button class="ssb3"><svg viewBox="0 0 200 60" aria-hidden="true"><rect x="1" y="1" width="198" height="58"/></svg>View portfolio</button>
.ssb3 { position: relative; width: 200px; max-width: 100%; height: 60px; border: 0; background: transparent; color: #111827; font: 700 15px Inter, system-ui, sans-serif; letter-spacing: .02em; cursor: pointer; }
.ssb3 svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
/* perimeter 512: top 198, right 58, bottom 198, left 58; 12px arms at each corner */
.ssb3 rect { fill: none; stroke: #111827; stroke-width: 2.5; stroke-dasharray: 12 174 24 34 24 174 24 34 12 0; transition: stroke-dasharray .5s ease; }
.ssb3:hover rect, .ssb3:focus-visible rect { stroke-dasharray: 99 0 128 0 128 0 128 0 29 0; }
.ssb3:focus-visible { outline: 2px dashed #2563eb; outline-offset: 6px; }
@media (prefers-reduced-motion: reduce) { .ssb3 rect { transition: none; } }

04. Icon That Draws on Hover

SVGIcon

A solid button whose arrow icon is redrawn line by line when the pointer arrives: the shaft first, then the head after a short delay. The label is the accessible name and the icon is aria-hidden, so screen readers hear only Continue.

View code
<button class="ssb4">Continue <svg viewBox="0 0 24 24" aria-hidden="true"><path class="shaft" d="M4 12h15" pathLength="1"/><path class="head" d="M13 6l6 6-6 6" pathLength="1"/></svg></button>
.ssb4 { display: inline-flex; align-items: center; gap: 10px; padding: 14px 22px; border: 0; border-radius: 12px; background: #4f46e5; color: #fff; font: 700 16px Inter, system-ui, sans-serif; cursor: pointer; }
.ssb4 svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.ssb4 path { stroke-dasharray: 1; stroke-dashoffset: 0; }
.ssb4:hover path, .ssb4:focus-visible path { animation: ssb4 .45s ease both; }
.ssb4:hover .head, .ssb4:focus-visible .head { animation-delay: .2s; }
@keyframes ssb4 { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
.ssb4:focus-visible { outline: 3px solid #a5b4fc; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .ssb4 path { animation: none !important; } }

05. Save Button with Drawn Tick

SVGState

Clicking toggles aria-pressed, and the pressed state swaps the label to Saved while a tick is drawn inside a filled circle. The state lives on the button itself, so assistive technology announces it as a toggle button that is pressed or not pressed.

View code
<button class="ssb5" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M7 12.5l3.2 3.2L17 9" pathLength="1"/></svg><span class="off">Save design</span><span class="on">Saved</span></button>
.ssb5 { display: inline-flex; align-items: center; gap: 10px; padding: 12px 20px 12px 14px; border: 1px solid #334066; border-radius: 999px; background: #151b2e; color: #e8ecff; font: 600 15px Inter, system-ui, sans-serif; cursor: pointer; }
.ssb5 svg { width: 24px; height: 24px; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.ssb5 circle { stroke: #475384; transition: stroke .3s, fill .3s; }
.ssb5 path { stroke: #0e1118; stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset .4s ease .1s; }
.ssb5 .on { display: none; }
.ssb5[aria-pressed="true"] { border-color: #34d399; }
.ssb5[aria-pressed="true"] circle { stroke: #34d399; fill: #34d399; }
.ssb5[aria-pressed="true"] path { stroke-dashoffset: 0; }
.ssb5[aria-pressed="true"] .on { display: inline; }
.ssb5[aria-pressed="true"] .off { display: none; }
.ssb5:focus-visible { outline: 2px solid #34d399; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .ssb5 * { transition: none !important; } }
document.querySelector('.ssb5').addEventListener('click', function () {
  this.setAttribute('aria-pressed', this.getAttribute('aria-pressed') === 'true' ? 'false' : 'true');
});

06. Gradient Stroke with Glow

SVGGradient

The outline is an SVG stroke painted with a linearGradient, which a CSS border cannot do on a rounded shape without extra layers. A drop-shadow filter adds a soft glow that brightens on hover, and the label stays plain white for contrast.

View code
<button class="ssb6"><svg viewBox="0 0 210 56" aria-hidden="true"><defs><linearGradient id="ssb6g" x1="0" x2="1"><stop offset="0" stop-color="#f472b6"/><stop offset=".5" stop-color="#a78bfa"/><stop offset="1" stop-color="#22d3ee"/></linearGradient></defs><rect x="1.5" y="1.5" width="207" height="53" rx="14"/></svg>Explore themes</button>
.ssb6 { position: relative; width: 210px; max-width: 100%; height: 56px; border: 0; border-radius: 14px; background: #10131f; color: #fff; font: 700 16px Inter, system-ui, sans-serif; cursor: pointer; }
.ssb6 svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.ssb6 rect { fill: none; stroke: url(#ssb6g); stroke-width: 3; filter: drop-shadow(0 0 3px rgba(167,139,250,.5)); transition: filter .3s; }
.ssb6:hover rect, .ssb6:focus-visible rect { filter: drop-shadow(0 0 9px rgba(167,139,250,.9)); }
.ssb6:focus-visible { outline: 2px solid #fff; outline-offset: 5px; }

07. Text Link with Wavy Underline

SVGLink

A text link whose underline is a hand-drawn wave in an inline SVG, drawn from left to right on hover and focus with stroke-dashoffset. Because it is a real link it keeps link semantics, and the wave stretches with the text because preserveAspectRatio is none.

View code
<a class="ssb7" href="#pricing">See pricing<svg viewBox="0 0 120 10" preserveAspectRatio="none" aria-hidden="true"><path d="M1 5 Q 8 0 15 5 T 29 5 T 43 5 T 57 5 T 71 5 T 85 5 T 99 5 T 113 5 T 119 5" pathLength="1"/></svg></a>
.ssb7 { position: relative; display: inline-block; padding-bottom: 10px; color: #0f172a; font: 800 22px Inter, system-ui, sans-serif; text-decoration: none; }
.ssb7 svg { position: absolute; left: 0; bottom: 0; width: 100%; height: 10px; overflow: visible; }
.ssb7 path { fill: none; stroke: #e11d48; stroke-width: 2.5; stroke-linecap: round; stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset .6s ease; vector-effect: non-scaling-stroke; }
.ssb7:hover path, .ssb7:focus-visible path { stroke-dashoffset: 0; }
.ssb7:focus-visible { outline: 2px solid #e11d48; outline-offset: 4px; border-radius: 4px; }
@media (prefers-reduced-motion: reduce) { .ssb7 path { transition: none; } }

About these CSS SVG Stroke Buttons

How stroke drawing works

Every SVG stroke can be broken into dashes with stroke-dasharray and shifted along its path with stroke-dashoffset. Set both to the length of the path and the line disappears; animate the offset back to zero and it draws itself in.

Let pathLength do the maths

Measuring a path's real length means JavaScript or guesswork. Add pathLength="100" (or 1) to the shape and the browser treats the path as that long, so the same dash values work whatever the button's size.

Keep the button a button

The SVG is decoration, so it gets aria-hidden="true" and pointer-events: none, and the visible label stays the accessible name. Hover effects are mirrored on :focus-visible so keyboard users see the same feedback.

Respect reduced motion

Looping stroke animations can distract. Each demo turns its animation off inside prefers-reduced-motion: reduce, leaving a static outline that still shows where the button is.

Frequently asked questions

Do SVG stroke buttons need JavaScript?

No. Only the save toggle uses a line of script to flip aria-pressed; the drawing effects are pure CSS.

Why does the border look thicker at the edges?

A stroke is centred on the shape's edge, so half of it falls outside. overflow: visible on the svg stops it being clipped.

Will these work in every browser?

Yes. Dash animations and pathLength work in all current browsers, including Safari.