When the icon you want isn’t in any shipped library, drop down to the AnimatedIcon core and pass your own SVG string.
The basics
<script>
import { AnimatedIcon } from 'svelte-animated-icon';
</script>
<AnimatedIcon
svg={`<circle cx="128" cy="128" r="96" fill="none" stroke="currentColor" stroke-width="16"/>`}
template="draw"
size={48}
/> svg is the inner markup - no <svg> wrapper, no bounding <rect fill="none">. The component renders the surrounding <svg viewBox="0 0 256 256"> and inlines your string inside it.
Why the 256 viewBox matters
Templates operate on a coordinate space of 0 0 256 256. As long as your shapes use coordinates in that range, every template works without modification.
<!-- ✓ works -->
<AnimatedIcon svg={`<rect x="32" y="32" width="192" height="192" fill="currentColor"/>`} template="pop" />
<!-- ✗ will draw but templates will scale oddly -->
<AnimatedIcon svg={`<rect x="0" y="0" width="24" height="24" fill="currentColor"/>`} template="pop" /> If you have SVG that doesn’t fit the 256 grid, the simplest fix is to wrap it in a <g transform="scale(…) translate(…)">. Better: redraw the path on the 256 grid - most icon editors export with viewBox="0 0 24 24", which is scale(~10.67) away.
Recognized shape types
Templates walk the SVG looking for shapes they can animate. Recognized:
pathcirclelinepolylinepolygonellipserect
If your icon uses something else (<use>, custom elements, <image>), the templates won’t see it. Stick to the seven above.
Color and stroke
Always prefer currentColor for fill and stroke - it lets the icon inherit from CSS. Avoid hard-coded fill="#000000" or stroke="black" unless you specifically want that color to override the parent.
<!-- ✓ follows theme -->
<AnimatedIcon svg={`<path d="..." fill="none" stroke="currentColor" stroke-width="16"/>`} />
<!-- ✗ ignores theme -->
<AnimatedIcon svg={`<path d="..." fill="#000"/>`} /> Sourcing SVG from Figma / Sketch / Illustrator
- Export as SVG.
- Open in a text editor.
- Find the outer
<svg …>tag. Inside, take everything between the opening tag and the closing</svg>- that’s yoursvgstring. - Remove any
<rect width="100%" height="100%" fill="none"/>bounding rect at the top. - Replace explicit colors with
currentColor. - Make sure paths use coordinates in 0–256.
For path data, svg-path-editor is a clean way to inspect and normalize.
Persisting custom icons
If you’re going to use a custom shape more than once, define it once and reuse:
<script>
import { AnimatedIcon } from 'svelte-animated-icon';
const MY_ICON = `<path d="M128 32 L224 224 L32 224 Z" fill="none" stroke="currentColor" stroke-width="16" stroke-linejoin="round"/>`;
</script>
<AnimatedIcon svg={MY_ICON} template="draw" />
<AnimatedIcon svg={MY_ICON} template="jelly" /> Or extract a Svelte component:
<!-- src/lib/icons/LogoTriangle.svelte -->
<script>
import AnimatedIcon from 'svelte-animated-icon/core/AnimatedIcon.svelte';
const SVG = `<path d="..." />`;
let { ...rest } = $props();
</script>
<AnimatedIcon svg={SVG} {...rest} /> That wrapper looks identical to the generated Phosphor components - drop it in any place you’d use a shipped icon.
A complete worked example
A loading spinner built from three rotating arcs:
<script>
import { AnimatedIcon } from 'svelte-animated-icon';
const SPINNER = `
<circle cx="128" cy="128" r="96" fill="none" stroke="currentColor"
stroke-width="16" stroke-linecap="round"
stroke-dasharray="150 600" stroke-dashoffset="0"/>
`;
</script>
<AnimatedIcon svg={SPINNER} template="spin" trigger="mount" loop size={32} /> The stroke-dasharray="150 600" makes only a quarter of the circle visible at any time. template="spin" rotates the whole SVG; combined with loop it produces a smooth perpetual spinner.