20 CSS SVG Stroke Buttons

17 Pure CSS3 Light JSPublished

20 hand-coded SVG stroke buttons where the outline draws itself with stroke-dasharray and stroke-dashoffset instead of sitting there as a static border. These cover the core draw-on-hover and pathLength normalisation, then gradient strokes, dashed marching-ant crops, focus-visible keyboard outlines, submit loading and disabled states, staggered button groups, and scroll-timeline reveals. Scoped under .svgb-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.

Related25 CSS Button Groups51 CSS Buttons20 CSS Loading Buttons15 CSS Glowing Border Buttons20 Tailwind SVG Stroke Buttons

Premium icon pickPartner: Nucleo

Affiliate link

Need icons for the components you just copied?

You just browsed 20 svg stroke button designs. Nucleo is a library of 44,000+ SVG icons in five families (Core, UI, Sharp, Pixel and Micro Bold), with web, Mac and Windows apps to manage them. The All Icons Bundle is $149 with lifetime updates.

Get the Nucleo bundle

Nucleo. We may earn a commission, at no extra cost to you.

01. SVG Stroke Draw Button Border on Hover (opens in a new tab)

Published

Pure CSS
Two pen passes, not a rectangle. A pencil ghost sits on the button at rest; on hover a marker inks one loose lap, overshoots the point it started from, and a second lighter pass follows 150ms behind at a different wobble phase — so the finished border is two non-parallel lines, the way a hand actually circles a word twice. The mechanism underneath is the plainest one there is: a dash as long as the path, pushed off the end, transitioned back to zero.

02. Stroke Dashoffset Button Outline Animation (opens in a new tab)

Published

Pure CSS
A studio transport bar, where the dash offset does three different jobs on three circular pads: play draws its ring once, record sends a single 40-unit runner orbiting forever, and the loop pad splits the circumference into a two-segment chase. The scrub line underneath uses the same property travelling in a straight line — same maths, no perimeter, no corners.

03. Corner Start Perimeter Draw Button (opens in a new tab)

Published

Pure CSS
A cyanotype drawing sheet where the border does not simply appear — it is measured. A crosshair head leaves the origin corner, rides the perimeter on a CSS motion path, and unrolls a surveyor’s tape of tick marks behind it while the hairline inks the edge underneath. Because the head, the tape and the ink all read the same d attribute, moving one M command moves all three origins at once — which is the whole point of the sheet.

04. Two Path Split Border Draw Button (opens in a new tab)

Published

Pure CSS
A cinema ticket, notches and perforation included, where the outline is genuinely two paths: from the perforation each half runs its own way round the stub and both land together on the opposite edge. The shape is not a rectangle, so the halves are measured rather than guessed — 392 units each — and the seam is exactly where the ticket is meant to tear.

05. Rounded Rect Stroke Button with rx Radius (opens in a new tab)

Published

Pure CSS
A radius inspector whose border is a contour sheet: all four candidate radii are drawn at once as faint outlines, so you can see that they share every straight edge and diverge only at the corners. Pick a value and that contour is the one that inks itself, with the perimeter arithmetic printed beside it and the active row highlighted. rx is driven as a CSS geometry property from a custom property, so the shape, the resting edge and the dash constant all change from one radio button — including the stadium case, where SVG's clamping rule is not the one CSS taught you.

06. Dashed Marching Ants Button Border (opens in a new tab)

Published

Pure CSS
Marching ants in the one place they were invented for: a crop selection over a real photograph, with corner handles, a rule-of-thirds grid and a dimension readout. The selection marches permanently because it is a live region; the Apply button next to it marches only while you are pointing at it — the same 18-unit loop, two completely different jobs.
Newsletter

Enjoying these demos? Get new ones by email.

A short email when a new collection ships. Unsubscribe any time.

at

Almost there: check your inbox to confirm.Manage

07. Gradient Stroke Button with SVG linearGradient (opens in a new tab)

Published

Pure CSS
A festival poster where the ticket CTA is the only lit object on the page: a wave-edged ribbon — no rect, no rx, a single closed path of eight quadratic crests per edge — whose stroke is a three-stop linearGradient in user space, hue-cycling through its own stops while the dash reveals it. One attribute — stroke="url(#id)" — replaces every background-clip gradient-border hack, and the two traps it brings with it are both on display.

08. Stroke Draw Button That Reverses on Mouse Leave (opens in a new tab)

Published

Pure CSS
Three mechanical keycaps on a graphite deck, each wired to a different technique: the first reverses from wherever the pointer left it, the second is a keyframe animation that cannot and snaps back, the third draws in 300ms and retracts in 760ms because entry and exit timing are declared in different rules. Sweep the pointer across all three and the difference is physical.

09. Focus Visible Stroke Outline for Keyboard Users (opens in a new tab)

Published

Pure CSS
A payment confirmation screen with its tab order made visible: every focusable control carries its stop number, and as focus lands the drawn stroke arrives on top of a real two-tone outline that is never animated. Press <kbd>Tab</kbd> in the preview — nothing here relies on the animation to tell you where you are, which is the whole point of the pattern.

10. Non Scaling Stroke Button That Resizes with Text (opens in a new tab)

Published

Pure CSS
The same call to action in six languages, right-to-left included, each button sized by its own text. Every frame is one fixed 200×56 viewBox stretched to whatever width the label needs, so the resting border is genuinely wrong — thin on the sides, heavy top and bottom. Hover does not draw a line: it wipes the corrected stroke across the button from the leading edge, so what you watch is a border becoming even rather than becoming present. Corners are square on purpose, which isolates the stroke error from the geometry error.

11. Stroke Draw Button with pathLength Normalization (opens in a new tab)

Published

Pure CSS
An arcade menu where every entry is a different silhouette — hexagon, diamond, shield, octagon and ring — and every one is animated by the same three lines of CSS. pathLength=&quot;1&quot; tells the renderer to treat each path as one unit long, so stroke-dasharray: 1 is a complete outline whether the geometry is 96 units round or 600.

13. Sequential Stroke and Label Fade Button (opens in a new tab)

Published

Pure CSS
An engraved invitation, choreographed in four beats: the outer rule draws, the inner rule follows it in, the serif line fades up and opens its letter-spacing, and the seal mark lands last. Leave the card and the sequence rewinds — seal, text, inner, outer — because the exit delays are declared separately and run faster than the entrance.

14. Icon Plus Border Stroke Draw Together (opens in a new tab)

Published

Pure CSS
A seven-day forecast strip where each day is a ring and a hand-drawn weather glyph — and both draw at once. The ring is hundreds of units long, the glyphs are a few dozen, so both carry pathLength=&quot;1&quot; and one duration keeps them in step. Multi-stroke glyphs are split into separate paths so the shaft can lead the arrowhead by 60ms.
Newsletter

Enjoying these demos? Get new ones by email.

A short email when a new collection ships. Unsubscribe any time.

at

Almost there: check your inbox to confirm.Manage

15. Stroke Draw Submit Button Loading State (opens in a new tab)

Published

Light JS
A checkout button with three real states carried entirely by its own outline: idle draws on hover, pending sends a quarter-perimeter segment orbiting the pill, and success closes the frame in mint and draws a tick while the label swaps to the amount paid. The script only sets data-state, aria-busy and a live-region sentence — every pixel of motion is CSS.

16. Stroke Draw Button Disabled and Error States (opens in a new tab)

Published

Light JS
A prescription review screen, where three states have to be visually and semantically distinct: the dose stepper's minus is genuinely disabled at the floor, the sign-off button is aria-disabled so a keyboard user can still reach it and learn what is missing, and the error frame appears instantly at full weight with an icon, a rewritten message and aria-invalid — never colour alone.

17. Scroll Triggered Button Stroke with animation-timeline (opens in a new tab)

Published

Pure CSS
A long-form essay whose inline calls to action draw themselves as they rise into the column, and undraw if you scroll back up — with a reading-progress ring in the corner driven by scroll() rather than view(). No IntersectionObserver, no scroll listener, and an @supports not block that hands unsupported engines a finished border instead of an invisible one.

18. Stroke Draw Button Group with Staggered Delay (opens in a new tab)

Published

Pure CSS
A departure board where hovering the platform rail sends the outlines out as a wave, and hovering one platform makes that platform the origin so the wave spreads outward from your pointer in both directions. Each tab carries its index as --i; every delay in the file is calc() on that one number.

19. Reduced Motion Fallback for Stroke Buttons (opens in a new tab)

Published

Pure CSS
An accessibility settings screen that demonstrates its own setting. Flip the motion switch and the three sample buttons swap their travelling strokes for instant, full-strength borders — the same block the real prefers-reduced-motion query applies, so what you see in the preview is exactly what a user with the OS setting on gets.

20. Variable Width Stroke Button with Text Length (opens in a new tab)

Published

Light JS
A weight ladder: six labels from three characters to thirty-five, each with its own stroke weight, dash rhythm and draw duration computed from the character count. The bottom pair is the control — the same two extremes at a fixed 2px — so you can see the optical problem the ladder solves rather than take it on trust.
FAQ

Frequently asked questions

How do you animate an SVG stroke to draw a button border on hover?
Put an SVG rectangle or path inside the button, then animate stroke-dashoffset. Set stroke-dasharray to the full path length and stroke-dashoffset to that same value, which hides the stroke entirely because every dash is pushed past the visible perimeter. On :hover, transition stroke-dashoffset to 0 and the dash slides back into view, reading as a pen drawing the outline. The rest of the button — background, label, padding — is normal HTML and CSS; only the border lives in the overlaid SVG. Demo 01 runs two passes over one loose stroke, and demo 15 reuses the same offset trick to carry a submit button's idle, pending and done states. The key is that dash length and offset must both equal the measured path length, or the stroke will start partly drawn or leave a gap that never closes.
What does pathLength do and why normalize it to 100?
pathLength lets you declare a virtual length for an SVG path, so the browser scales the real geometry to that number when resolving stroke-dasharray and stroke-dashoffset. Set pathLength="100" on every path and the shape now measures 100 units regardless of its actual perimeter — a hexagon, a diamond, a shield and a ring all draw with identical dash math. That is exactly what demo 11 does across five different silhouettes: one shared dasharray: 100 and dashoffset: 100 animates every one to full draw in the same duration. Without it you would have to call getTotalLength() in JavaScript per shape, or hand-measure each path and hardcode a different dasharray. Normalizing to 100 also makes the numbers read as percentages, so a half-drawn border is simply dashoffset: 50.
Why does my SVG stroke get thicker when the button grows?
By default SVG strokes scale with the element, so a button that stretches with its text or its container also stretches its 2px border into something fatter and uneven on the sides that scaled most. The fix is vector-effect: non-scaling-stroke on the stroked path — it tells the renderer to paint the stroke at its declared width in screen pixels no matter how the geometry is transformed or how the viewBox maps to the box. Demo 10 relies on this to keep one call to action crisp across six languages of wildly different length, and demo 12 uses it so a skewed blade shape keeps an even outline. Pair it with preserveAspectRatio="none" on a rect that must fill a fluid button, and set the rect width and height to the SVG's own 100% so it tracks the button box rather than a fixed pixel size.
How do I keep a keyboard focus outline visible on these buttons?
Draw the focus ring with the same stroked SVG the hover animation uses, but gate it on :focus-visible so it only appears for keyboard and assistive-tech users, not on mouse click. Do not remove the native outline blindly — if you set outline: none you must replace it with a stroke that has real contrast against the button and its background, or you have broken keyboard operability. Demo 09 makes the whole tab order legible by giving every focusable control its own numbered stroke stop, so a keyboard user can see where focus will land next. Keep the focus stroke wide enough to notice (2px or more) and give it a color that passes contrast in both the resting and pressed states. Because :focus-visible and :hover are separate selectors, a control can show its draw-in on hover and its ring on focus without the two fighting.
How do you make stroke button animations respect reduced motion?
Wrap every continuous or draw-in animation in @media (prefers-reduced-motion: reduce) and inside it set the stroke to its final drawn state immediately — stroke-dashoffset: 0 with transition: none and animation: none. The button must still communicate hover and focus; you just swap the motion for an instant state change, so the border appears fully drawn rather than sweeping in. Never leave a demo with motion that cannot be turned off, and never let the reduced-motion branch hide the outline entirely, or you have removed the affordance along with the animation. Demo 19 is built around this: it is a settings screen that flips its own motion switch and shows the three sample buttons dropping their sweep for a static outline. Author the reduced-motion rules at the same time as the animated ones, not as an afterthought — it is far easier to keep the final state correct when both paths are written together.
Can I trigger the stroke draw on scroll instead of hover?
Yes, with a scroll-driven animation. Define the draw as @keyframes that move stroke-dashoffset from the full path length to 0, then attach it with animation-timeline: view() so progress is tied to the element entering the viewport rather than to a clock. Use animation-range to say where in the element's view pass the draw should start and finish. Demo 17 uses this for inline calls to action in a long essay: each border draws as it rises into the column and undraws as it leaves, because a view-timeline animation is reversible with scroll direction. Two cautions — support is still uneven, so keep a sensible resting state for browsers that ignore animation-timeline, and honor prefers-reduced-motion here too, since scroll-linked motion can be more disorienting than hover motion. It is best reserved for editorial content where the draw reinforces reading position.
Why does my stroke animation start halfway drawn or leave a gap?
Almost always a mismatch between stroke-dasharray and stroke-dashoffset. To hide a stroke completely at rest, dasharray and dashoffset must both equal the path length, so the single dash sits entirely off the visible perimeter; if the offset is smaller than the array you see the tail already drawn, and if it is larger you get a gap that never closes. When you hardcode a length that does not match the real geometry — after a resize, a viewBox change or an rx corner radius that alters the perimeter — the numbers drift and the seam shows. The robust fix is pathLength="100" so the declared length is always 100 regardless of shape, as in demo 11, or reading getTotalLength() at runtime. Also confirm the child combinator in your selector did not get escaped: an inline style block can turn a raw greater-than into > and silently drop the rule that sets the dash values.
When should I not use an SVG stroke button?
Skip it when a plain CSS border does the job. A static rectangular outline needs no SVG at all — border plus border-radius is lighter and has none of the dash-math pitfalls. Reach for the SVG only when you genuinely need a drawn perimeter: a non-rectangular shape, a border that animates as a single continuous stroke, a gradient stroke via linearGradient as in demo 07, or per-corner control the box model cannot express. Stroke draws are also a poor fit for high-frequency UI like table row actions or dense toolbars, where the sweep becomes noise rather than delight; save them for primary calls to action. And never let the decorative stroke carry meaning a screen reader cannot reach — the real <button> and its text must stand on their own, with the SVG marked aria-hidden="true", so the animation is enhancement and never the only signal that a control is focused, loading or disabled.

Related collections

15 CSS 3D Tilt Hover Cards preview

15 CSS 3D Tilt Hover Cards

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 CSS Animated Cards preview

24 CSS Animated Cards

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 CSS Avatars preview

22 CSS Avatars

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.

Search CodeFronts

Loading…