svg-interactive
v0.3.1
Published
Transform any SVG into an interactive form with embedded input/output fields. Works with draw.io, Figma, Inkscape, and custom SVGs.
Maintainers
Readme
SVG Interactive Diagram
Transform ANY SVG diagram into an interactive form with embedded input/output fields.
Works seamlessly with draw.io, Figma, Inkscape, or custom SVGs. No vendor lock-in.
Quick Start • Examples • API Docs • Contributing
✨ Why This Library?
Ever wanted to make your SVG diagrams interactive but faced these problems?
- ❌ Locked into one tool's proprietary format
- ❌ Can't embed HTML inputs directly in SVG
- ❌ Complex setup just to add a few interactive fields
- ❌ Have to rebuild your diagram from scratch
This library solves all of that:
- ✅ Works with ANY SVG tool - no vendor lock-in
- ✅ Uses SVG
foreignObjectto embed real HTML inputs - ✅ Dead simple setup - just add IDs or data attributes
- ✅ Keep using your favorite design tool
🚀 Quick Start
Installation
npm install svg-interactive
# or
yarn add svg-interactiveBasic Usage (30 seconds)
Add IDs to your SVG (in Inkscape, Figma, or any editor):
<rect id="input-temperature" .../> <rect id="output-result" .../>Use in React:
import { SvgInteractive, parseSVG } from 'svg-interactive'; // Step 1: Parse your SVG to extract field mappings const svgContent = await fetch('/diagram.svg').then(r => r.text()); const { mappings } = parseSVG(svgContent, { patterns: [ { prefix: 'input-', type: 'input' }, { prefix: 'output-', type: 'output' } ] }); // Step 2: Render the interactive SVG <SvgInteractive mappings={mappings} svgContent={svgContent} onOutputCompute={(inputs) => ({ result: `You entered: ${inputs.temperature}` })} />
That's it! Your SVG now has interactive input/output fields.
📚 How It Works
The library uses SVG's <foreignObject> element to overlay HTML inputs at exact positions:
- Load your SVG
- Find elements with matching IDs
- Get their bounding boxes
- Create
<foreignObject>overlays - Render HTML inputs/outputs inside
🎯 Flexible Field Matching
The library matches SVG elements using attribute-based patterns - you can match on any SVG attribute (id, class, data-*, etc.):
Match by ID (Default - Inkscape, Figma, Custom SVGs)
<rect id="input-temp" x="10" y="10" width="100" height="30"/>const { mappings } = parseSVG(svgContent, {
patterns: [
{ prefix: 'input-', type: 'input' }, // Matches id="input-*"
{ prefix: 'output-', type: 'output' }
]
});Match by Data Attribute (draw.io)
For draw.io SVGs, use the dedicated parser which automatically looks for custom metadata:
In draw.io:
- Right-click shape → Edit Data
- Add property:
data-id=input-field-temp - Export as SVG
import { parseDrawIoSVG } from 'svg-interactive';
const { mappings } = parseDrawIoSVG(svgContent, {
patterns: [
{ prefix: 'input-field-', type: 'input' },
{ prefix: 'output-field-', type: 'output' }
]
});Match by Custom Attribute
const { mappings } = parseSVG(svgContent, {
patterns: [
{ attribute: 'class', prefix: 'field-input-', type: 'input' },
{ attribute: 'data-field', regex: /^output-(.+)$/, type: 'output' }
]
});Match by Exact ID List
When you have a fixed set of elements with specific IDs:
<rect id="temperature" x="10" y="10" width="100" height="30"/>
<rect id="pressure" x="10" y="50" width="100" height="30"/>
<rect id="volume" x="10" y="90" width="100" height="30"/>const { mappings } = parseSVG(svgContent, {
patterns: [
{ ids: ['temperature', 'pressure', 'volume'], type: 'input' }
]
});Use cases for ids array:
- Fixed set of known elements
- Legacy SVGs with inconsistent naming
- When you don't control the ID naming convention
- Maximum explicitness and control (e.g., 100+ specific sensors in a complex diagram)
💡 Features
- 🎨 Universal SVG Support - draw.io, Figma, Inkscape, Adobe Illustrator, hand-coded
- 🔄 Flexible Matching - Match any SVG attribute (id, class, data-*, etc.)
- 🎯 Pattern-Based - Prefix or regex matching with per-pattern configuration
- ⚛️ React 18+ - Built with modern React and createRoot API
- 🎨 Fully Customizable - Themes, CSS variables, custom components
- 📊 Debug Mode - Built-in debugging panel
- 💪 TypeScript - Complete type definitions with strict mode
- 🚀 Next.js Ready - Works out of the box
🌐 Interactive Landing Page & Playground
This repository now ships with a polished React + Vite site (see /site) that showcases every example and includes a fully interactive playground:
npm run site:dev— run the landing page locally with hot reloadnpm run site:build— produce the static assets undersite/distnpm run site:preview— preview the production build
The site imports the real components and presets from /examples so content always stays in sync with the package. A dedicated workflow (.github/workflows/site.yml) builds the site on every push to main and deploys it to GitHub Pages—just enable “GitHub Actions” under Settings → Pages and you’ll have a live playground/landing page with zero manual steps.
📖 Examples
Calculator with Custom Styling
import { SvgInteractive, parseSVG } from 'svg-interactive';
const svgContent = await fetch('/calculator.svg').then(r => r.text());
const { mappings } = parseSVG(svgContent, {
patterns: [
{ prefix: 'input-', type: 'input' },
{ prefix: 'output-', type: 'output' }
]
});
<SvgInteractive
mappings={mappings}
svgContent={svgContent}
onOutputCompute={(inputs) => ({
sum: (parseFloat(inputs.a || '0') + parseFloat(inputs.b || '0')).toString()
})}
theme="bordered"
inputClassName="px-2 py-1 border-2 border-blue-500 rounded"
outputClassName="px-2 py-1 bg-green-50 border-2 border-green-500 rounded"
/>Custom React Components
import { SvgInteractive, parseDrawIoSVG } from 'svg-interactive';
const svgContent = await fetch('/diagram.svg').then(r => r.text());
const { mappings } = parseDrawIoSVG(svgContent, {
patterns: [
{ prefix: 'input-', type: 'input' },
{ prefix: 'output-', type: 'output' }
]
});
<SvgInteractive
mappings={mappings}
svgContent={svgContent}
renderInput={(props) => (
<input
type="number"
value={props.value}
onChange={(e) => props.onChange(e.target.value)}
className="custom-input"
min="0"
max="100"
/>
)}
renderOutput={(props) => (
<div className="custom-output">
<strong>{props.name}:</strong> {props.value}
</div>
)}
/>Tailwind Styling
<SvgInteractive
mappings={mappings}
svgContent={svgContent}
theme="none"
inputClassName="w-full h-full px-3 py-2 border-2 border-blue-500 rounded-lg focus:ring-2"
outputClassName="w-full h-full px-3 py-2 bg-green-50 border-2 border-green-500 rounded-lg"
/>📐 SVG Preparation
Quick guides for each tool:
- draw.io / diagrams.net - Use data-id attributes
- Figma - Rename layers with prefixes
- Inkscape - Set element IDs via Object Properties
- Adobe Illustrator - Use Layers panel names
- Hand-coded SVG - Just add
idattributes
🔧 API Reference
Parser Function
First, parse your SVG to extract field mappings. parseSVG will auto-detect Draw.io SVGs.
// Generic parser for all SVG types
parseSVG(svgContent: string, options: ParseOptions): ParseResult
// Optimized parser for Draw.io SVGs
parseDrawIoSVG(svgContent: string, options: ParseOptions): ParseResultParseOptions:
interface ParseOptions {
patterns: FieldPattern[]; // Field matching patterns
mode?: 'data-id' | 'direct-id'; // Optional: force specific mode
}
// FieldPattern is a discriminated union - must have exactly ONE matching strategy
type FieldPattern =
| { prefix: string; type: 'input' | 'output'; attribute?: string } // Match by prefix
| { regex: RegExp; type: 'input' | 'output'; attribute?: string } // Match by regex
| { ids: string[]; type: 'input' | 'output'; attribute?: string }; // Match by ID listParseResult:
interface ParseResult {
mappings: FieldMapping[]; // Use this for SvgInteractive
errors: string[]; // Any parsing errors encountered
metadata: {
tool: 'drawio' | 'generic'; // drawio or generic (Figma, Inkscape, etc.)
detectedMode: 'data-id' | 'direct-id';
attributesUsed: string[]; // Attributes queried during parsing
};
}<SvgInteractive> Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| mappings | FieldMapping[] | Yes | Field mappings from parser |
| svgContent | string | Yes | Raw SVG string |
| onInputChange | (name, value, all) => void | No | Fired when any input changes |
| onOutputCompute | (inputs) => outputs | No | Compute all outputs from inputs |
| outputValues | Record<string, string> | No | Controlled output values |
| onOutputUpdate | Record<string, fn> | No | Per-field output callbacks |
| renderInput | (props) => ReactNode | No | Custom input renderer |
| renderOutput | (props) => ReactNode | No | Custom output renderer |
| theme | 'default' \| 'minimal' \| 'bordered' \| 'none' | No | Built-in theme |
| inputClassName | string | No | CSS class for inputs |
| outputClassName | string | No | CSS class for outputs |
| inputStyle | CSSProperties | No | Inline styles for inputs |
| outputStyle | CSSProperties | No | Inline styles for outputs |
| debug | boolean | No | Show debug panel |
| onDebugInfo | (info) => void | No | Debug callback |
🎨 Styling
Built-in Themes
<SvgInteractive theme="default" /> // Blue inputs, green outputs
<SvgInteractive theme="minimal" /> // Simple borders
<SvgInteractive theme="bordered" /> // Bold borders with shadows
<SvgInteractive theme="none" /> // No default stylingCSS Variables
:root {
--svg-input-border: #3B82F6;
--svg-input-bg: #FFFFFF;
--svg-output-border: #10B981;
--svg-output-bg: #F0FDF4;
--svg-field-font-size: 12px;
}Import Styles
import 'svg-interactive/styles';🐛 Debug Mode
<SvgInteractive
debug={true}
onDebugInfo={(info) => {
console.log('Mode:', info.matchingMode); // 'data-id' or 'direct-id'
console.log('Fields:', info.totalFields); // Total fields found
console.log('Inputs:', info.inputFields); // Input fields
console.log('Outputs:', info.outputFields); // Output fields
}}
/>🌐 Browser Support
✅ Chrome/Edge | ✅ Firefox | ✅ Safari
Requires SVG foreignObject support (all modern browsers).
🤝 Contributing
Contributions are welcome! Please read CONTRIBUTING.md for details.
Development Setup
git clone https://github.com/m98/svg-interactive
cd svg-interactive
npm install
npm run dev # Watch mode
npm test # Run tests
npm run quality # Full quality check📝 License
MIT © Mohammad
🔗 Links
⭐ Show Your Support
If this library helped you, please consider giving it a ⭐ on GitHub!
Built with ❤️ for the open-source community
