20 CSS SVG Stroke Buttons
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 linkNeed 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.
Nucleo. We may earn a commission, at no extra cost to you.
02. Stroke Dashoffset Button Outline Animation (opens in a new tab)
03. Corner Start Perimeter Draw Button (opens in a new tab)
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)
05. Rounded Rect Stroke Button with rx Radius (opens in a new tab)
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)
07. Gradient Stroke Button with SVG linearGradient (opens in a new tab)
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)
09. Focus Visible Stroke Outline for Keyboard Users (opens in a new tab)
10. Non Scaling Stroke Button That Resizes with Text (opens in a new tab)
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)
pathLength="1" 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.12. Dual Layer Stroke Button with Offset Trailing Path (opens in a new tab)
13. Sequential Stroke and Label Fade Button (opens in a new tab)
14. Icon Plus Border Stroke Draw Together (opens in a new tab)
pathLength="1" and one duration keeps them in step. Multi-stroke glyphs are split into separate paths so the shaft can lead the arrowhead by 60ms.15. Stroke Draw Submit Button Loading State (opens in a new tab)
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)
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)
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)
--i; every delay in the file is calc() on that one number.19. Reduced Motion Fallback for Stroke Buttons (opens in a new tab)
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)
Frequently asked questions
How do you animate an SVG stroke to draw a button border on hover?
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?
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?
: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?
@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?
@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?
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?
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
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
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
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.


















