Building Vectorio: A Browser-Based SVG to Component Generator
The 5-Minute Tax Nobody Talks About
You export an icon from Figma. Drop the SVG into your project. And before it's actually usable, you do this dance:
- Strip the
xmlns:figma,data-name,data-figma-*noise. - Rename
id="gradient-1"so it doesn't collide with the next icon you import. - Remove hardcoded
width="64"andheight="64"that make sizing through CSS impossible. - Wrap it in a component, lift
fillandstroketo props, fix the casing for JSX (fill-rule→fillRule). - Decide if it's decorative (
aria-hidden) or labeled (role="img"+<title>).
It's never more than five minutes. But you do it every time. And if you forget the ID rename and ship two icons that share gradient-1, one of them quietly stops rendering correctly in production.
I wanted that work to be one paste away. That's Vectorio.
vectorio.app · github.com/berkinduz/vectorio
What It Does
Paste an SVG (or drop a folder of them), pick your framework, get back idiomatic, tree-shakable component code.
Targets: React, Vue, Svelte, Solid, with optional TypeScript and Tailwind output. Everything happens in the browser — no upload, no server, no account.
Why Browser-Only
The honest reason: SVG cleaning is a pure function. Input → output, no state, no secrets to protect, no rate-limited API to call. Putting a server in front of that is a liability, not a feature.
Concrete consequences of going zero-backend:
- Privacy by default. Your icons never leave your machine. For people working on unreleased products, this matters more than it sounds.
- Static hosting on Vercel. No cold starts, no cost beyond bandwidth.
- Shareable state via URL. Encode the converter config into the URL itself. No database, no auth, but a teammate can open your exact setup with one click.
The whole app is React 19 + Vite 8 with vanilla CSS. No UI library — the surface is small enough that one stylesheet is shorter than the Tailwind config would have been. (Same instinct that drove Mockator to keep its server stateless: if the work doesn't need state, don't ship state.)
The Bug That Drives the Whole Tool: ID Collisions
If you've ever imported two SVGs with gradients into the same page and watched one of them silently render gray, you've hit this:
<!-- icon-a.svg -->
<linearGradient id="gradient-1">...</linearGradient>
<rect fill="url(#gradient-1)" />
<!-- icon-b.svg -->
<linearGradient id="gradient-1">...</linearGradient>
<rect fill="url(#gradient-1)" />
Both icons reference #gradient-1. The browser resolves both refs to whichever <linearGradient> it parses first. The second icon loses its gradient.
Vectorio fixes this by namespacing every internal ID with the component name during conversion:
<linearGradient id="HomeIcon__gradient-1">...</linearGradient>
<rect fill="url(#HomeIcon__gradient-1)" />
Boring. Mechanical. The kind of thing that should never be your problem.
Multi-Framework from One Parse
The interesting design decision: parse the SVG once into an intermediate AST, then have separate emitters per framework. Adding Solid after the React/Vue/Svelte work was already there cost about a day, mostly because the abstraction had earned its keep.
Each emitter handles its own quirks:
- React: kebab-case to camelCase (
stroke-width→strokeWidth),class→className, props spread. - Vue:
:fillbinding,<script setup>if TS is on. - Svelte:
{props}shorthand, reactive size props. - Solid: like React but with the small Solid-specific differences that bite you at runtime.
Auto-detected props are the part I'm proudest of. Vectorio scans the SVG for fill, stroke, and dimensions, and lifts them to component props with sensible defaults — so the output is actually parameterized, not just static markup wrapped in a function.
Batch Mode: From a Folder to a Publishable Package
Single icons are the warm-up. The real win is batch mode.
Drop a folder of SVGs in. Vectorio:
- Cleans and converts every file.
- Auto-namespaces duplicate filenames so
arrow.svgfrom two subfolders don't collide. - Generates an
index.tsbarrel export. - Generates a
package.jsonand aREADME.md. - Hands you back a ZIP.
Built with JSZip entirely client-side. Drop the unzipped folder into a monorepo, run npm publish, and you have an icon library. The boring pipeline a designer-handoff usually owns, compressed into one upload.
Accessibility, Three Modes
This was a late addition that I now think should have been there from day one. SVGs in components can be:
- Decorative —
aria-hidden="true", no semantic weight. - Labeled —
role="img"with a<title>lifted from a prop. - Hidden — for
<defs>-only sprites that shouldn't render at all.
Vectorio asks once and emits the right markup. Most icon libraries default to "decorative everywhere" and quietly fail accessibility audits. The cost of doing this right at codegen time is zero; doing it right after the fact across hundreds of components is a project.
The Stack, Briefly
- React 19 + Vite 8 for the app.
- Vitest + jsdom for the parser tests, plus a smoke-check script that runs against a fixture directory of real-world Figma exports.
- JSZip for client-side packaging.
- Custom prerender script after
vite buildfor SEO-friendly static HTML on the marketing surface. - Vercel Analytics, Vercel for hosting.
The build pipeline is more disciplined than the project size strictly demands — prerender, smoke check, vitest, lint, typecheck — but the parser is the kind of code where a regression silently corrupts user output, and I'd rather notice in CI than in someone's repo.
What's Next
A few things on the list, in rough order of how much I want them:
- VS Code extension. Right-click an SVG, get the component in the clipboard.
- CLI.
npx vectorio ./icons --out ./componentsfor build-step usage. - Figma plugin. Skip the export step entirely.
- Sprite-sheet output. For teams who'd rather ship one optimized SVG than 200 components.
Try It
vectorio.app — paste an SVG, pick a framework. The whole thing is open source under MIT: github.com/berkinduz/vectorio.
If you find an SVG that breaks the parser, open an issue with the file attached. Those are the most useful bug reports — every fixture in the test suite started as someone's broken export.