{"id":45245856,"url":"https://github.com/milanofthe/dom2svg","last_synced_at":"2026-02-20T22:19:00.160Z","repository":{"id":338705184,"uuid":"1158651343","full_name":"milanofthe/dom2svg","owner":"milanofthe","description":"Convert DOM elements to clean, self-contained SVG files.","archived":false,"fork":false,"pushed_at":"2026-02-16T01:06:15.000Z","size":9355,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-02-16T06:48:44.496Z","etag":null,"topics":["dom","dom-to-svg","export","html-to-svg","javascript","react-flow","svelteflow","svg","typescript","vector-graphics"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/milanofthe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-15T18:00:39.000Z","updated_at":"2026-02-16T01:06:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/milanofthe/dom2svg","commit_stats":null,"previous_names":["milanofthe/dom2svg"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/milanofthe/dom2svg","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/milanofthe%2Fdom2svg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/milanofthe%2Fdom2svg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/milanofthe%2Fdom2svg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/milanofthe%2Fdom2svg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/milanofthe","download_url":"https://codeload.github.com/milanofthe/dom2svg/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/milanofthe%2Fdom2svg/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29667088,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-20T19:49:36.704Z","status":"ssl_error","status_checked_at":"2026-02-20T19:44:05.372Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dom","dom-to-svg","export","html-to-svg","javascript","react-flow","svelteflow","svg","typescript","vector-graphics"],"created_at":"2026-02-20T22:18:59.458Z","updated_at":"2026-02-20T22:19:00.149Z","avatar_url":"https://github.com/milanofthe.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dom2svg\n\nConvert DOM elements to clean, self-contained SVG files.\n\nBuilt for exporting node-based editors (SvelteFlow, React Flow, etc.) to vector graphics. Handles mixed HTML/SVG structures that existing libraries fail on.\n\n## Demo\n\nA node-based pipeline editor — DOM on the left, exported SVG on the right:\n\n![Pipeline editor export](media/demo-pipeline.png)\n\nReal-world UI components (stat cards, avatars, alerts, buttons, progress bars, charts, palettes, tables, toasts, tags, toggles, breadcrumbs, donut charts, skeleton loaders, keyboard shortcuts):\n\n![Real-world components](media/demo-components.png)\n\n55+ CSS features rendered as DOM elements and their SVG counterparts:\n\n![Feature showcase](media/demo-features.png)\n\n## Install\n\n```bash\nnpm install github:milanofthe/dom2svg\n```\n\n## CLI — Vector Screenshots\n\nCapture any web page as a vector SVG from the command line. Uses headless Chrome via Puppeteer to load the page, then runs dom2svg against it.\n\nRequires Chrome/Chromium installed locally. Puppeteer-core is included as a dev dependency.\n\n```bash\n# Full page\nnpx dom2svg https://example.com -o page.svg\n\n# Specific element with background and padding\nnpx dom2svg https://news.ycombinator.com -s \"#hnmain\" -b white -p 12 -o hn.svg\n\n# Pipe to stdout\nnpx dom2svg https://example.com \u003e page.svg\n\n# Wait for JS-rendered content\nnpx dom2svg https://app.example.com --wait 3000 -o app.svg\n\n# Custom viewport size\nnpx dom2svg https://example.com --width 1920 --height 1080 -o wide.svg\n```\n\n**Options:**\n\n| Flag | Description | Default |\n|------|-------------|---------|\n| `-o, --output \u003cfile\u003e` | Output file (default: stdout) | stdout |\n| `-s, --selector \u003ccss\u003e` | CSS selector for target element | `body` |\n| `-b, --background \u003ccolor\u003e` | SVG background color | transparent |\n| `-p, --padding \u003cpx\u003e` | Padding around the element | `0` |\n| `--width \u003cpx\u003e` | Viewport width | `1280` |\n| `--height \u003cpx\u003e` | Viewport height | `800` |\n| `--wait \u003cms\u003e` | Extra wait after page load | `0` |\n| `--chrome \u003cpath\u003e` | Chrome executable path | auto-detect |\n\nChrome is auto-detected on Windows, macOS, and Linux. Override with `--chrome` or the `CHROME_PATH` environment variable.\n\n## Quick Start\n\n```ts\nimport { domToSvg } from \"dom2svg\";\n\nconst element = document.querySelector(\"#my-editor\");\nconst result = await domToSvg(element);\n\n// Download as .svg file\nresult.download(\"export.svg\");\n```\n\n## Examples\n\n### Export with white background and padding\n\n```ts\nconst result = await domToSvg(element, {\n  background: \"#ffffff\",\n  padding: 20,\n});\n```\n\n### Download or get the SVG as a string/blob\n\n```ts\nconst result = await domToSvg(element);\n\n// Trigger browser download\nresult.download(\"diagram.svg\");\n\n// Get SVG markup (e.g. for saving to a server)\nconst svgString = result.toString();\n\n// Get a Blob (e.g. for FormData upload)\nconst blob = result.toBlob();\n```\n\n### Exclude elements from export\n\n```ts\n// By CSS selector\nconst result = await domToSvg(element, {\n  exclude: \".toolbar, .minimap, [data-no-export]\",\n});\n\n// By predicate\nconst result = await domToSvg(element, {\n  exclude: (el) =\u003e el.tagName === \"BUTTON\",\n});\n```\n\n### Text-to-path (font-independent output)\n\nConvert text to `\u003cpath\u003e` elements so the SVG renders identically without any fonts installed. Requires [opentype.js](https://github.com/opentypejs/opentype.js) (bundled dependency).\n\n```ts\nconst result = await domToSvg(element, {\n  textToPath: true,\n  fonts: {\n    // Simple: family → URL\n    \"Inter\": \"/fonts/Inter-Regular.woff2\",\n\n    // Detailed: family → config with weight/style\n    \"Inter\": {\n      url: \"/fonts/Inter-Bold.woff2\",\n      weight: \"700\",\n      style: \"normal\",\n    },\n  },\n});\n```\n\n### Custom element handler\n\nOverride rendering for specific elements:\n\n```ts\nconst result = await domToSvg(element, {\n  handler: (el, ctx) =\u003e {\n    // Replace a placeholder with a custom SVG shape\n    if (el.classList.contains(\"chart-placeholder\")) {\n      const circle = ctx.svgDocument.createElementNS(\n        \"http://www.w3.org/2000/svg\",\n        \"circle\",\n      );\n      circle.setAttribute(\"cx\", \"50\");\n      circle.setAttribute(\"cy\", \"50\");\n      circle.setAttribute(\"r\", \"40\");\n      circle.setAttribute(\"fill\", \"#3b82f6\");\n      return circle;\n    }\n    return null; // fall through to default rendering\n  },\n});\n```\n\n### Export a SvelteFlow editor\n\n```svelte\n\u003cscript\u003e\n  import { domToSvg } from \"dom2svg\";\n\n  async function exportDiagram() {\n    const editor = document.querySelector(\".svelte-flow\");\n    const result = await domToSvg(editor, {\n      background: \"#ffffff\",\n      padding: 24,\n      exclude: \".svelte-flow__controls, .svelte-flow__minimap\",\n    });\n    result.download(\"diagram.svg\");\n  }\n\u003c/script\u003e\n\n\u003cbutton onclick={exportDiagram}\u003eExport SVG\u003c/button\u003e\n```\n\n### Export a React Flow editor\n\n```tsx\nimport { domToSvg } from \"dom2svg\";\n\nfunction ExportButton() {\n  const handleExport = async () =\u003e {\n    const editor = document.querySelector(\".react-flow\");\n    const result = await domToSvg(editor, {\n      background: \"#ffffff\",\n      padding: 24,\n      exclude: \".react-flow__controls, .react-flow__minimap\",\n    });\n    result.download(\"flowchart.svg\");\n  };\n\n  return \u003cbutton onClick={handleExport}\u003eExport SVG\u003c/button\u003e;\n}\n```\n\n## API\n\n### `domToSvg(element, options?)`\n\nConverts a DOM element tree into a self-contained SVG.\n\n**Parameters:**\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `element` | `Element` | The root DOM element to convert |\n| `options` | `DomToSvgOptions` | Optional configuration (see below) |\n\n**Returns:** `Promise\u003cDomToSvgResult\u003e`\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `svg` | `SVGSVGElement` | The generated SVG element |\n| `toString()` | `string` | Serialized SVG with XML declaration |\n| `toBlob()` | `Blob` | SVG as `image/svg+xml` blob |\n| `download(filename?)` | `void` | Triggers browser download |\n\n### Options\n\n```ts\ninterface DomToSvgOptions {\n  /** Background color for the SVG (default: transparent) */\n  background?: string;\n\n  /** Padding around the captured area in px (default: 0) */\n  padding?: number;\n\n  /** CSS selector or predicate to exclude elements */\n  exclude?: string | ((element: Element) =\u003e boolean);\n\n  /** Convert text to \u003cpath\u003e elements using opentype.js (default: false) */\n  textToPath?: boolean;\n\n  /** Font mapping for text-to-path (family name → URL or config) */\n  fonts?: Record\u003cstring, string | { url: string; weight?: string; style?: string }\u003e;\n\n  /** Custom element handler — return SVGElement to override, null for default */\n  handler?: (element: Element, context: RenderContext) =\u003e SVGElement | null;\n}\n```\n\n## Supported CSS Features\n\n| Feature | Support |\n|---------|---------|\n| **Backgrounds** | |\n| Background colors | Full |\n| Linear gradients | Full (correct diagonal angles on non-square elements) |\n| Radial gradients | Full (circle and ellipse, rasterized via Canvas) |\n| Conic gradients | Full (rasterized via Canvas) |\n| Multiple backgrounds | Full (layered in correct CSS order) |\n| Background size/position | Full (`contain`, `cover`, explicit sizes) |\n| Background images (`url()`) | Full (inlined as data URLs) |\n| **Borders \u0026 Outlines** | |\n| Borders (uniform and per-side) | Full (solid, dashed, dotted) |\n| Border radius (uniform and non-uniform) | Full (including pill shapes) |\n| Outline | Full (solid, dashed, dotted with offset) |\n| **Shadows** | |\n| Box shadow | Full (outer and inset, multiple, spread, blur) |\n| Text shadow | Full (single and multiple, via SVG filters) |\n| **CSS Filters** | |\n| `blur()` | Full (via `feGaussianBlur`) |\n| `brightness()` | Full (via `feComponentTransfer`) |\n| `contrast()` | Full (via `feComponentTransfer`) |\n| `drop-shadow()` | Full (via `feDropShadow`) |\n| `grayscale()` | Full (via `feColorMatrix`) |\n| `hue-rotate()` | Full (via `feColorMatrix`) |\n| `invert()` | Full (via `feComponentTransfer`) |\n| `opacity()` | Full (via `feComponentTransfer`) |\n| `saturate()` | Full (via `feColorMatrix`) |\n| `sepia()` | Full (via `feColorMatrix`) |\n| Filter chaining | Full (multiple filters compose in order) |\n| **Layout \u0026 Clipping** | |\n| CSS transforms | Full (translate, rotate, scale, skew, matrix) |\n| Transform origin | Full |\n| Opacity | Full |\n| Overflow clipping | Full (`hidden`, `clip`, `scroll`, `auto`) |\n| `clip-path` | Full (`inset`, `circle`, `ellipse`, `polygon`, `path`) |\n| Z-index / stacking contexts | Full (CSS 2.2 paint order) |\n| **Elements** | |\n| Inline SVGs | Full (deep clone with ID namespacing) |\n| `\u003cimg\u003e` elements | Full (inlined as data URLs, border-radius clipping) |\n| `\u003ccanvas\u003e` elements | Full (via `toDataURL()`) |\n| Form elements | Full (`\u003cinput\u003e`, `\u003cselect\u003e`, `\u003ctextarea\u003e` with multiline) |\n| Pseudo-elements (`::before`, `::after`) | Full (text content, browser-measured positioning) |\n| List markers | Full (disc, circle, square, decimal) |\n| **Text** | |\n| Text rendering | Full (`\u003ctext\u003e` elements, or `\u003cpath\u003e` with `textToPath`) |\n| Text decoration | Full (underline, line-through) |\n| Text transform | Full (uppercase, lowercase, capitalize) |\n| Text overflow (ellipsis) | Full (appends ellipsis character) |\n| Letter spacing | Full |\n| **Visibility** | |\n| `visibility: hidden` | Correctly skipped (children still rendered) |\n| `display: none` | Correctly skipped |\n| `opacity: 0` | Rendered as group with `opacity=\"0\"` (subtree preserved) |\n\n## Architecture\n\n```\nsrc/\n├── index.ts              # domToSvg() entry point\n├── types.ts              # All TypeScript interfaces\n├── core/\n│   ├── traversal.ts      # DOM tree walking\n│   └── styles.ts         # CSS property parsing\n├── renderers/\n│   ├── html-element.ts   # HTML → SVG (backgrounds, borders, overflow, pseudo)\n│   ├── svg-element.ts    # SVG cloning with ID namespacing\n│   └── text-node.ts      # Text → \u003ctext\u003e or \u003cpath\u003e\n├── assets/\n│   ├── images.ts         # Image/canvas → data URL inlining\n│   ├── fonts.ts          # Font loading + text-to-path (opentype.js)\n│   ├── gradients.ts      # CSS gradient → SVG gradient / rasterized image\n│   ├── filters.ts        # CSS filters → SVG filters (blur, grayscale, sepia, etc.)\n│   ├── box-shadow.ts     # box-shadow → SVG filter / mask\n│   ├── clip-path.ts      # clip-path → SVG clipPath\n│   └── text-shadow.ts    # text-shadow → SVG filter\n├── transforms/\n│   ├── parse.ts          # CSS transform string parsing\n│   ├── matrix.ts         # 2D affine matrix operations\n│   └── svg.ts            # CSS transform → SVG transform attribute\n└── utils/\n    ├── dom.ts            # SVG namespace helpers, DOM type guards\n    ├── id-generator.ts   # Unique ID generation\n    └── geometry.ts       # Bounding box and rounded-rect path utilities\n```\n\nSingle runtime dependency: [opentype.js](https://github.com/opentypejs/opentype.js) (only used when `textToPath` is enabled).\n\nThe CLI additionally uses [puppeteer-core](https://pptr.dev/) for headless Chrome automation.\n\n## Development\n\n```bash\nnpm install\nnpm run build        # ESM + CJS + CLI bundles via tsup\nnpm run type-check   # TypeScript strict mode\nnpm run test         # 211 unit tests via Vitest\nnpm run test:watch   # Watch mode\nnpm run demo         # Visual demo at localhost:5173\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmilanofthe%2Fdom2svg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmilanofthe%2Fdom2svg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmilanofthe%2Fdom2svg/lists"}