{"id":51011733,"url":"https://github.com/yoxjames/svg4kt","last_synced_at":"2026-06-21T03:31:13.891Z","repository":{"id":344655409,"uuid":"1182601897","full_name":"yoxjames/svg4kt","owner":"yoxjames","description":"SVG implemented as a Kotlin Multiplatform type safe builder (DSL). Like kotlinx-html, but for SVG!","archived":false,"fork":false,"pushed_at":"2026-06-07T20:52:32.000Z","size":712,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-07T22:24:06.314Z","etag":null,"topics":["htmx","kotlin","kotlin-multiplatform","svg","web"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yoxjames.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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-03-15T18:28:27.000Z","updated_at":"2026-06-07T20:52:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/yoxjames/svg4kt","commit_stats":null,"previous_names":["yoxjames/svg4k","yoxjames/svg4kt"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/yoxjames/svg4kt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoxjames%2Fsvg4kt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoxjames%2Fsvg4kt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoxjames%2Fsvg4kt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoxjames%2Fsvg4kt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yoxjames","download_url":"https://codeload.github.com/yoxjames/svg4kt/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoxjames%2Fsvg4kt/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34593128,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-21T02:00:05.568Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["htmx","kotlin","kotlin-multiplatform","svg","web"],"created_at":"2026-06-21T03:31:12.269Z","updated_at":"2026-06-21T03:31:13.876Z","avatar_url":"https://github.com/yoxjames.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# svg4kt (SVG For Kotlin)\n![Maven Central Version](https://img.shields.io/maven-central/v/dev.jamesyox/svg4kt)\n[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0)\n[![Kotlin](https://img.shields.io/badge/kotlin-2.4.0-blue.svg?logo=kotlin)](http://kotlinlang.org)\n\nThis library is an implementation of the current [second edition of the SVG 1.1 spec](https://www.w3.org/TR/SVG11/) \nas a Kotlin DSL (or type safe builder). This library strives for maximal type safety and the avoidance of string typing \nas much as possible. This library makes use of multiple [context parameters](https://kotlinlang.org/docs/context-parameters.html) feature in Kotlin. The library's design \n(and some of the implementation) is based on [kotlinx-html](https://github.com/Kotlin/kotlinx.html). Like kotlinx-html, this library is fully multiplatform \nand can generate DOM elements on the js and wasmJs targets.\n\n## Getting Started\nAn alpha version of this library is published on Maven Central.\nCoordinates:\n\ngroupId: `dev.jamesyox`\n\nartifactId: `svg4kt`\n\nversion: `0.1.0`\n\nIf you use Gradle you should be able to add the following to your dependencies to use svg4kt:\n```kotlin\nimplementation(\"dev.jamesyox:svg4kt:0.1.0\")\n```\n\nYou are now free to use this in a project, however you _must_ enable context parameters. This library is based around\nthat feature. If you are using Kotlin 2.4.0 or greater, then context parameters are stable, and you do not need to \nenable anything. \n\n```kotlin\n// build.gradle.kts\nkotlin {\n    compilerOptions {\n        // This is required for Kotlin 2.3.X and no longer needed for Kotlin 2.4.0\n        freeCompilerArgs.add(\"-Xcontext-parameters\")\n    }\n}\n// ....\nimplementation(\"dev.jamesyox:svg4kt:0.1.0\")\n```\n\n### Stream\nIf you are familiar with the `kotlinx-html` then the syntax should feel pretty familiar:\n```kotlin\nval actual = svgString(isPrettyPrint = true) {\n  svg {\n    viewBox = ViewBox(0, 0, 200, 100)\n    path {\n      fill = SvgPaint.None\n      // Convenience method for stroke = SvgPaint.Color(SvgColor.LightGrey)\n      stroke(SvgColor.LightGrey) \n      d {\n        M(20, 50)\n        C(20, -50, 180, 150, 180, 50)\n        C(180, -50, 20, 150, 20, 50)\n        Z\n      }\n    }\n    circle {\n      r = 5.none\n      // Convenience method for fill = SvgPaint.Color(SvgColor.Red)\n      fill(SvgColor.Red)\n      animateMotion {\n        dur = Dur.ClockValue(10.seconds)\n        repeatCount = RepeatCount.Indefinite\n        path {\n          M(20, 50)\n          C(20, -50, 180, 150, 180, 50)\n          C(180, -50, 20, 150, 20, 50)\n          Z\n        }\n      }\n    }\n  }\n}\nprintln(svgString)\n```\nWould output\n```\n\u003csvg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    viewBox=\"0 0 200 100\"\u003e\n    \u003cpath\n        fill=\"none\"\n        stroke=\"lightgrey\"\n        d=\"M 20 50 C 20 -50 180 150 180 50 C 180 -50 20 150 20 50 Z\" /\u003e\n    \u003ccircle\n        r=\"5\"\n        fill=\"red\"\u003e\n        \u003canimateMotion\n            dur=\"10s\"\n            repeatCount=\"indefinite\"\n            path=\"M 20 50 C 20 -50 180 150 180 50 C 180 -50 20 150 20 50 Z\" /\u003e\n    \u003c/circle\u003e\n\u003c/svg\u003e\n```\n\nWhich, if rendered by a browser, would look like:\n![AnimationMotion Mozilla Example SVG](./samples/AnimateMotionMozillaExample.svg)\n*Source: https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/animateMotion*\n\nI eventually plan to add more docs but a good overview of the syntax can be found in the tests I have written so far.\n\nhttps://github.com/yoxjames/svg4kt/tree/main/src/commonTest/kotlin/dev/jamesyox/svg4kt/tags\n\nAlso like `kotlinx-html` this builder can be used on any `Appendable`. This is done with the `appendSVG { ... }` \nextension fun.\n\nOn JVM this could be used to create a file like:\n```kotlin\nFileWriter(\"image.svg\").use { it.appendSVG { svg { /* ... */ } } }\n```\n\n### DOM\nLike `kotlinx-html` this project can be used on JS and WasmJS to generate DOM elements instead of just text. For\nexample:\n\n```kotlin\n// Available only in JS and WasmJS source sets\nval rect: SVGElement // Allowed since this uses the `callsInPlace` Contract. You can use this to add listeners, etc.\nval domElement: SVGElement = createSvg {\n    svg {\n        rect = rect {\n            // ...\n        }\n    }\n}\n\ndomElement.onclick = { /* ... */ } // This is a DOM element so you can use browser DOM methods\nrect.onclick = { /* ... */ } // Same here.\n```\n\n## Using with kotlinx-html\nAs of today (though I hope to get a PR into kotlinx-html that will make this better), there's not a great way to interop\nwith `kotlinx-html`. Ideally I want to do something like this:\n```kotlin\nhtml { // kotlinx-html\n    body { // kotlin-html\n        svg { // svg4kt \u003e\u003e\u003e THIS DOES NOT CURRENTLY WORK\n            // ... // svg4kt\n        }\n    }\n}\n```\nWith context parameters this should be possible. On most targets it pretty much is. You could easily write an extension\nlike this:\n\n```kotlin\nprivate fun HTMLTag.svg4kt(\n    block: context(AttributeConsumer, @SvgTagDSL Svg) () -\u003e Unit\n) {\n    unsafe {\n        raw(svgString { dev.jamesyox.svg4kt.tags.svg { block() } })\n    }\n}\n```\n\nThis function bridges `kotlinx-html` to `svg4kt` by basically tapping into the unsafe raw text api \nfrom `kotlinx-html`. This works for many use cases but if you attempt this same approach on a `TagConsumer\u003cT\u003e` that uses\nthe DOM, this will not work. Appending text will not generate dom elements. For that you'll need something much more\nugly like this:\n\n```kotlin\nfun \u003cT\u003e TagConsumer\u003cHTMLElement\u003e.svg4kt(\n    block: context(dev.jamesyox.svg4kt.TagConsumer\u003cSVGElement\u003e, RootContainer) () -\u003e T\n): T {\n    val tagConsumer = JsDomTagConsumer(document)\n    val output = block(tagConsumer, RootContainer)\n    val svgDom = tagConsumer.output()\n    val hackDiv = div { } // We simply need to create a DOM element to access the parent (wasteful)\n    val currentNode = hackDiv.parentNode\n    currentNode?.removeChild(hackDiv)\n\n    currentNode?.let {\n        svgDom.forEach { child -\u003e it.appendChild(child) }\n    }\n    return output\n}\n```\nI'm hoping to open a PR with `kotlinx-html` soon that should make seamless interop possible. \n\nPR: https://github.com/Kotlin/kotlinx.html/pull/296 (currently merged awaiting the next release)\n\nIf you want to see a full project using the library like this please check out my Kastro Demo project. It's a simple\nstatic site that can show you information about the sun and moon via SVGs done locally in your browser!\n\nhttps://github.com/yoxjames/kastro-demo\n\n## Convenience Functions\nMany web standards make use of \"union\" types for what's valid for a given attribute. Lets take an element like `\u003ccircle\u003e`\nand an attribute like `fill`. This takes an input type called `Paint` which MDN defines as such:\n\n```text\npaint ::= none | \u003ccolor\u003e | \u003curl\u003e [none | \u003ccolor\u003e]? | context-fill | context-stroke\n```\n\nIn Kotlin, since we dont have proper union type support, this would typically be defined this as a sealed interface. \nIn fact, you can see my exact implementation in `SvgPaint`.\n\nIf you wanted to set a circle to be filled with the color red you would have to do something like this:\n\n```kotlin\ncircle {\n    fill = SvgPaint.Color(SvgColor.Red)\n}\n```\n\nThat's rather verbose having to constantly provide boxed types. When not in Kotlin you could simple set fill to Red,\nrather than worrying about boxing. \n\nIdeally I'd like to be able to override setters, so I could support something like this which would be very natural\neven without proper union types:\n\n```kotlin\n// THIS DOES NOT WORK\ncircle {\n    fill = SvgColor.Red\n}\n```\n\nSetter overrides are not supported in Kotlin. I would encourage you to upvote the following issue. If this gets added to\nthe language I'll make sure this library supports it.\nhttps://youtrack.jetbrains.com/issue/KT-4075\n\nSince I cannot do this I decided to use a workaround and follow a convention. I am able to use function overriding to \nemulate this functionality. For many properties I have simple defined functions named the same as the property that can take\nthe types that I boxed into the \"union.\" So for fill you can simply pass a color like this:\n\n```kotlin\ncircle {\n    fill(SvgColor.Red) // This is the compromise\n}\n```\n\nI wish I could get the \"setter\" syntax here but this accomplishes the goal even if it reads a little worse.\n\n## Unsafe DSL\nThis library may have bugs. A lot of those bugs may be around typing and scoping. Hypothetically lets say for some reason `cx` was\nnow allowed on `Circle` elements. You could force the issue like this:\n\n```kotlin\ncircle {\n    unsafe { // Any attribute is allowed here\n        cx = 5.none\n    }\n}\n```\n\nYou can also create invalid svg element hierarchies. Like normally you cant have a `circle` inside of a `circle` but \nwith the `Unsafe` API you can do anything you want, no matter how unwise! Generally this is intended to work around bugs\nby providing a version of the API with some type checking off.\n\n```kotlin\ncircle {\n    unsafe {\n        circle { /* ... */ }\n    }\n}\n```\n\nWould yield the invalid `\u003ccircle\u003e\u003ccircle\u003e\u003c/circle\u003e\u003c/circle\u003e` result.\n\n### Manual Attributes\n`unsafe { ... }` also allows you to add arbitrary references. For instance:\n```kotlin\nval expected = \"\"\"\n    \u003csvg\n        xmlns=\"http://www.w3.org/2000/svg\"\u003e\n        \u003ccircle\n            not-real=\"madeup\" /\u003e\n    \u003c/svg\u003e\n\"\"\".trimIndent()\nval actual = svgString(isPrettyPrint = true) {\n    svg {\n        circle {\n            unsafe {\n                attr[\"not-real\"] = \"madeup\"\n            }\n        }\n    }\n}\nassertEquals(expected, actual)\n```\nIn this case we used `attr` which is available inside the `unsafe` scope to add an attribute called `not-real` and set\nit to `madeup`. You can use this to set attributes that exist to arbitrary values as well.\n\n### Custom Elements\nYou can also use `unsafe { ... }` to construct arbitrary elements via `customElement(...)`:\n```kotlin\nval expected = \"\"\"\n    \u003csvg\n        xmlns=\"http://www.w3.org/2000/svg\"\u003e\n        \u003cfake\n            fr=\"5\"\u003e\n            \u003ccircle /\u003e\n        \u003c/fake\u003e\n    \u003c/svg\u003e\n\"\"\".trimIndent()\nval actual = svgString(isPrettyPrint = true) {\n    svg {\n        unsafe {\n            // Create element \u003cfake\u003e\n            customElement(\"fake\") {\n                // Any attribute can be used in custom elements\n                fr = 5.none\n                // Any elements can be added to custom elements\n                circle {\n\n                }\n            }\n        }\n    }\n}\n```\n\n### Cascading Unsafe\nUnsafe is meant to work around issues you may find in the library or perhaps to do things I had not considered. However,\nit does have some limitations. One big one is that `unsafe { ... }` only makes things unsafe inside the lambda provided.\n\nFor instance, you cannot do something like:\n\n```kotlin\ncircle {\n    unsafe {\n        // unsafe applies at this level!\n        rect { // This compiles but is not a valid SVG!\n            // unsafe does not apply at this level!\n            fr = 5.none // `fr` does not apply to rect so this wont compile!\n        }\n    }\n}\n```\nThis won't be allowed even with unsafe because `unsafe { ... }` is marked with the `SvgTagDsl` marker which is marked\nwith `DskMarker`.\n\nhttps://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-dsl-marker/\n\nIf you want to truly turn off safety you'll need the `unsafeCascading { ... }` function which starts an unsafe scope\nwith no DSL marker letting you go wild:\n\n```kotlin\ncircle {\n    unsafeCascading {\n        // unsafeCascading applies at this level!\n        rect { // This compiles but is not a valid SVG!\n            // unsafe still applies at this level too!\n            fr = 5.none // This compiles but is not a valid SVG!\n        }\n    }\n}\n```\n\n## Contributing\nContributions are welcome. If you find a bug and want to fix it, feel free to do so and open a PR. Additionally, if you\nare looking for places to contribute, simply search for TODO and you'll see every area I have identified as lacking.\n\n## Citations\n* A lot of this project was inspired by `kotlinx-html` by JetBrains, the overall design was reused but reimagined using multiple context parameters. A lot of the tag consumption logic was based on the tag consumption in kotlinx-html\n  \n  https://github.com/Kotlin/kotlinx.html\n* The documentation and visibility of each element/attribution on specific parents was largely based on Mozilla's SVG documentation. I highly recommend this as a resource to anyone wanting to learn more about SVG. A lot of my test cases come from examples in this document.\n  \n  https://developer.mozilla.org/en-US/docs/Web/SVG\n* Last but not least is the actual SVG 1.1 spec which is the actual protocol this library models.\n  \n  https://www.w3.org/TR/SVG11/","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyoxjames%2Fsvg4kt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyoxjames%2Fsvg4kt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyoxjames%2Fsvg4kt/lists"}