{"id":15066882,"url":"https://github.com/randomhashtags/swift-htmlkit","last_synced_at":"2025-07-13T10:33:08.266Z","repository":{"id":257464409,"uuid":"858342760","full_name":"RandomHashTags/swift-htmlkit","owner":"RandomHashTags","description":"Write HTML and HTMX using Swift Macros.","archived":false,"fork":false,"pushed_at":"2025-07-04T22:05:49.000Z","size":2229,"stargazers_count":50,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-11T04:00:25.974Z","etag":null,"topics":["dsl","html","htmx","server-side-swift","swift","swift-library","swift-macro","swift-macros"],"latest_commit_sha":null,"homepage":"","language":"Swift","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/RandomHashTags.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","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},"funding":{"github":"RandomHashTags","thanks_dev":"d/gh/randomhashtags","buy_me_a_coffee":"randomhashtags"}},"created_at":"2024-09-16T18:21:00.000Z","updated_at":"2025-07-04T22:05:53.000Z","dependencies_parsed_at":"2024-09-16T23:45:49.476Z","dependency_job_id":"f5c730ba-a0ed-4cde-9016-add2f7aba0fd","html_url":"https://github.com/RandomHashTags/swift-htmlkit","commit_stats":null,"previous_names":["randomhashtags/swift-htmlkit"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/RandomHashTags/swift-htmlkit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RandomHashTags%2Fswift-htmlkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RandomHashTags%2Fswift-htmlkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RandomHashTags%2Fswift-htmlkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RandomHashTags%2Fswift-htmlkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RandomHashTags","download_url":"https://codeload.github.com/RandomHashTags/swift-htmlkit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RandomHashTags%2Fswift-htmlkit/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265128646,"owners_count":23715624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["dsl","html","htmx","server-side-swift","swift","swift-library","swift-macro","swift-macros"],"created_at":"2024-09-25T01:13:31.658Z","updated_at":"2025-07-13T10:33:08.191Z","avatar_url":"https://github.com/RandomHashTags.png","language":"Swift","funding_links":["https://github.com/sponsors/RandomHashTags","https://thanks.dev/d/gh/randomhashtags","https://buymeacoffee.com/randomhashtags"],"categories":[],"sub_categories":[],"readme":"Write HTML using Swift Macros. Supports HTMX via global attributes.\n\n\u003ca href=\"https://swift.org\"\u003e\u003cimg src=\"https://img.shields.io/badge/Swift-5.9+-F05138?style=\u0026logo=swift\" alt=\"Requires at least Swift 5.9\"\u003e\u003c/a\u003e \u003cimg src=\"https://img.shields.io/badge/Platforms-Any-gold\"\u003e \u003ca href=\"https://discord.com/invite/VyuFQUpcUz\"\u003e\u003cimg src=\"https://img.shields.io/badge/Chat-Discord-7289DA?style=\u0026logo=discord\"\u003e\u003c/a\u003e \u003ca href=\"https://github.com/RandomHashTags/swift-htmlkit/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/License-Apache_2.0-blue\" alt=\"Apache 2.0 License\"\u003e\u003c/a\u003e\n\n## Table of Contents\n\n- [Why](#why)\n- [Usage](#usage)\n  - [Basic](#basic)\n  - [Advanced](#advanced)\n  - [HTMX](#htmx)\n- [Benchmarks](#benchmarks)\n  - [Static](#static)\n  - [Dynamic](#dynamic)\n  - [Conclusion](#conclusion)\n- [Contributing](#contributing)\n\n## Why\n- Swift Macros are powerful, efficient and removes any runtime overhead (dynamic HTML requires runtime)\n- Alternative libraries may not fit all situations and may restrict how the html is generated, manipulated, or cost a constant performance overhead (middleware, rendering, result builders, etc)\n- HTML macros enforce safety, can be used anywhere, and compile directly to strings by default\n- The output is minified at no performance cost\n\n## Usage\n\n### Basic\n\n\u003cdetails\u003e\n\u003csummary\u003eHow do I use this library?\u003c/summary\u003e\n\nUse the `#html(encoding:lookupFiles:innerHTML:)` macro. All parameters, for the macro and default HTML elements, are optional by default. The default HTML elements are generated by an internal macro.\n\n#### Macros\n\n\u003cdetails\u003e\n\u003csummary\u003ehtml\u003c/summary\u003e\n\nRequires explicit type annotation due to returning the inferred concrete type.\n\n```swift\n\n#html\u003cT: CustomStringConvertible\u003e(\n  encoding: HTMLEncoding = .string,\n  lookupFiles: [StaticString] = [],\n  _ innerHTML: CustomStringConvertible \u0026 Sendable...\n) -\u003e T\n\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\n\u003csummary\u003eanyHTML\u003c/summary\u003e\n\nSame as `#html` but returning an existential.\n\n```swift\n\n#anyHTML(\n  encoding: HTMLEncoding = .string,\n  lookupFiles: [StaticString] = [],\n  _ innerHTML: CustomStringConvertible \u0026 Sendable...\n)\n\n```\n\n\u003c/details\u003e\n\n#### HTMLElement\n\nAll default HTML elements conform to the `HTMLElement` protocol and contain their appropriate element attributes. They can be declared when you initialize the element or be changed after initialization by accessing the attribute variable directly.\n\nThe default initializer for creating an HTML Element follows this syntax:\n\n```swift\n\n\u003chtml element name\u003e(\n  attributes: [\u003cglobal attribute\u003e] = [],\n  \u003celement specific attribute\u003e: \u003cvalue\u003e? = nil,\n  _ innerHTML: CustomStringConvertible \u0026 Sendable...\n)\n\n```\n\n#### Examples\n\n```swift\n// \u003cdiv class=\"dark\"\u003e\u003cp\u003eMacros are beautiful\u003c/p\u003e\u003c/div\u003e\n#html(\n  div(attributes: [.class([\"dark\"])],\n    p(\"Macros are beautiful\")\n  )\n)\n\n// \u003ca href=\"https://github.com/RandomHashTags/litleagues\" target=\"_blank\"\u003e\u003c/a\u003e\n#html(\n  a(href: \"https://github.com/RandomHashTags/litleagues\", target: ._blank)\n)\n\n// \u003cinput id=\"funny-number\" max=\"420\" min=\"69\" name=\"funny_number\" step=\"1\" type=\"number\" value=\"69\"\u003e\n#html(\n  input(\n    attributes: [.id(\"funny-number\")],\n    max: 420,\n    min: 69,\n    name: \"funny_number\",\n    step: 1,\n    type: .number,\n    value: \"69\"\n  )\n)\n\n// html example\nlet test:String = #html(\n  html(\n    body(\n        div(\n            attributes: [\n                .class([\"dark-mode\", \"row\"]),\n                .draggable(.false),\n                .hidden(.true),\n                .inputmode(.email),\n                .title(\"Hey, you're pretty cool\")\n            ],\n            \"Random text\",\n            div(),\n            a(\n                div(\n                    abbr()\n                ),\n                address()\n            ),\n            div(),\n            button(disabled: true),\n            video(autoplay: true, controls: false, preload: .auto, src: \"https://github.com/RandomHashTags/litleagues\", width: .centimeters(1)),\n        )\n    )\n  )\n)\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eHow do I escape HTML?\u003c/summary\u003e\n\nThe compiled output automatically escapes source breaking html characters **known only at compile time**.\n\n\nYou can also use the `#escapeHTML(innerHTML:)` macro to escape data known at compile time.\n\nIf you're working with **runtime** data:\n\n- `\u003cstring\u003e.escapeHTML(escapeAttributes:)`\n  - mutates `self` escaping HTML and, optionally, attribute characters\n- `\u003cstring\u003e.escapeHTMLAttributes()`\n  - mutates `self` escaping only attribute characters\n- `\u003cstring\u003e.escapingHTML(escapeAttributes:)`\n  - returns a copy of `self` escaping HTML and, optionally, attribute characters\n- `\u003cstring\u003e.escapingHTMLAttributes()`\n  - returns a copy of `self` escaping only attribute characters\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eHow do I encode variables?\u003c/summary\u003e\n\nUsing String Interpolation.\n\n\u003e You will get a compiler warning saying *interpolation may introduce raw HTML*.\n\u003e \n\u003e Its up to you whether or not to suppress this warning or escape the HTML at runtime using a method described above.\n\u003e \n\u003e Swift HTMLKit tries to [promote](https://github.com/RandomHashTags/swift-htmlkit/blob/94793984763308ef5275dd9f71ea0b5e83fea417/Sources/HTMLKitMacros/HTMLElement.swift#L423) known interpolation at compile time with an equivalent `StaticString` for the best performance. It is currently limited due to macro expansions being sandboxed and lexical contexts/AST not being available for the macro argument types. This means referencing content in an html macro won't get promoted to its expected value. [Read more about this limitation](https://forums.swift.org/t/swift-lexical-lookup-for-referenced-stuff-located-outside-scope-current-file/75776/6).\n\n#### Example\n\n```swift\nlet string:String = \"any string value\", integer:Int = -69, float:Float = 3.141592\n\n// ✅ DO\nlet _:String = #html(p(\"\\(string); \\(integer); \\(float)\"))\nlet _:String = #html(p(\"\\(string)\", \"; \", String(describing: integer), \"; \", float.description))\n\nlet integer_string:String = String(describing: integer), float_string:String = String(describing: float)\nlet _:String = #html(p(string, \"; \", integer_string, \"; \", float_string))\n\n// ❌ DON'T; compiler error; compile time value cannot contain interpolation\nlet _:StaticString = #html(p(\"\\(string); \\(integer); \\(float)\"))\nlet _:StaticString = #html(p(\"\\(string)\", \"; \", String(describing: integer), \"; \", float.description))\nlet _:StaticString = #html(p(string, \"; \", integer_string, \"; \", float_string))\n\n```\n\n\u003c/details\u003e\n\n### Advanced\n\n\u003cdetails\u003e\n\u003csummary\u003eI need a custom element!\u003c/summary\u003e\n\nUse the default `custom(tag:isVoid:attributes:innerHTML:)` html element.\n\n#### Example\n\nWe want to show the [Apple Pay button](https://developer.apple.com/documentation/apple_pay_on_the_web/displaying_apple_pay_buttons_using_javascript#3783424):\n```swift\n#html(\n  custom(\n    tag: \"apple-pay-button\",\n    isVoid: false,\n    attributes: [\n      .custom(\"buttonstyle\", \"black\"),\n      .custom(\"type\", \"buy\"),\n      .custom(\"locale\", \"el-GR\")\n    ]\n  )\n)\n```\nbecomes\n```html\n\u003capple-pay-button buttonstyle=\"black\" type=\"buy\" locale=\"el-GR\"\u003e\u003c/apple-pay-button\u003e\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eI need a custom attribute!\u003c/summary\u003e\n\nUse the `.custom(id:value:)` global attribute.\n\n#### Example\n\nWe want to show the [Apple Pay button](https://developer.apple.com/documentation/apple_pay_on_the_web/displaying_apple_pay_buttons_using_javascript#3783424):\n```swift\n#html(\n  custom(\n    tag: \"apple-pay-button\",\n    isVoid: false,\n    attributes: [\n      .custom(\"buttonstyle\", \"black\"),\n      .custom(\"type\", \"buy\"),\n      .custom(\"locale\", \"el-GR\")\n    ]\n  )\n)\n```\nbecomes\n```html\n\u003capple-pay-button buttonstyle=\"black\" type=\"buy\" locale=\"el-GR\"\u003e\u003c/apple-pay-button\u003e\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eI need to listen for events!\u003c/summary\u003e\n\n\u003e \u003cstrong\u003eWARNING\u003c/strong\u003e\n\u003e\n\u003e Inline event handlers are an outdated way to handle events.\n\u003e\n\u003e General consensus considers this \\\"bad practice\\\" and you shouldn't mix your HTML and JavaScript.\n\u003e\n\u003e This remains deprecated to encourage use of other techniques.\n\u003e\n\u003e Learn more at https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#inline_event_handlers_—_dont_use_these.\n\nUse the `.event(\u003cevent type\u003e, \"\u003cvalue\u003e\")` global attribute.\n\n#### Example\n\n```swift\n#html(\n  div(\n    attributes: [\n      .event(.click, \"doThing()\"),\n      .event(.change, \"doAnotherThing()\")\n    ]\n  )\n)\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eI need the output as a different type!\u003c/summary\u003e\n\nDeclare the encoding you want in the `#html` macro.\n\n```swift\n\n#html(\n  encoding: HTMLEncoding = .\u003ctype\u003e\n)\n\n```\n\n[Currently supported types](https://github.com/RandomHashTags/swift-htmlkit/blob/main/Sources/HTMLKitUtilities/HTMLEncoding.swift):\n- `string` -\u003e `String`/`StaticString`\n- `utf8Bytes` -\u003e An array of `UInt8` (supports any collection `where Element == UInt8`)\n- `utf16Bytes` -\u003e An array of `UInt16` (supports any collection `where Element == UInt16`)\n- `utf8CString` -\u003e `ContiguousArray\u003cCChar\u003e`\n- `foundationData` -\u003e `Foundation.Data`/`FoundationEssentials.Data`\n  - You need to `import Foundation` or `import FoundationEssentials` to use this!\n- `byteBuffer` -\u003e `NIOCore.ByteBuffer`\n  - You need to `import NIOCore` to use this! Swift HTMLKit does not depend on `swift-nio`!\n- `custom(\"\u003cencoding logic\u003e\")` -\u003e A custom type conforming to `CustomStringConvertible`\n  - Use `$0` to reference the compiled HTML (as a String without the delimiters)\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\n\u003csummary\u003eI need to use raw HTML!\u003c/summary\u003e\n\nUse the `#rawHTML(encoding:lookupFiles:innerHTML:)` and `#anyRawHTML(encoding:lookupFiles:innerHTML:)` macros.\n\n#### Examples\n\n```swift\n\nvar expected = \"\u003c!DOCTYPE html\u003e\u003chtml\u003edude\u0026dude\u003c/html\u003e\"\nvar result:String = #rawHTML(\"\u003c!DOCTYPE html\u003e\u003chtml\u003edude\u0026dude\u003c/html\u003e\")\n#expect(expected == result)\n\nexpected = \"\u003c!DOCTYPE html\u003e\u003chtml\u003e\u003cp\u003etest\u0026lt;\u0026gt;\u003c/p\u003edude\u0026dude bro\u0026amp;bro\u003c/html\u003e\"\nresult = #html(html(#anyRawHTML(p(\"test\u003c\u003e\"), \"dude\u0026dude\"), \" bro\u0026bro\"))\n#expect(expected == result)\n\n```\n\n\u003c/details\u003e\n\n### HTMX\n\n\u003cdetails\u003e\n\n\u003csummary\u003eHow do I use HTMX?\u003c/summary\u003e\n\nUse the `.htmx(\u003chtmx attribute\u003e)` global attribute. All HTMX 2.0 attributes are supported (including Server Sent Events \u0026 Web Sockets).\n\n#### Examples\n\n```swift\n\n// \u003cdiv hx-boost=\"true\"\u003e\u003c/div\u003e\nvar string:StaticString = #html(div(attributes: [.htmx(.boost(.true))]))\n\n// \u003cdiv hx-get=\"/test\"\u003e\u003c/div\u003e\nstring = #html(div(attributes: [.htmx(.get(\"/test\"))]))\n\n// \u003cdiv hx-on::abort=\"bruh()\"\u003e\u003c/div\u003e\nstring = #html(div(attributes: [.htmx(.on(.abort, \"bruh()\"))]))\n\n// \u003cdiv hx-on::after-on-load=\"test()\"\u003e\u003c/div\u003e\nstring = #html(div(attributes: [.htmx(.on(.afterOnLoad, \"test()\"))]))\n\n// \u003cdiv hx-on:click=\"thing()\"\u003e\u003c/div\u003e\nstring = #html(div(attributes: [.htmx(.onevent(.click, \"thing()\"))]))\n\n// \u003cdiv hx-preserve\u003e\u003c/div\u003e\nstring = #html(div(attributes: [.htmx(.preserve(true))]))\n\n// \u003cdiv sse-connect=\"/connect\"\u003e\u003c/div\u003e\nstring = #html(div(attributes: [.htmx(.sse(.connect(\"/connect\")))]))\n\n// \u003cdiv ws-connect=\"/chatroom\"\u003e\u003c/div\u003e\nstring = #html(div(attributes: [.htmx(.ws(.connect(\"/chatroom\")))]))\n\n// \u003cdiv hx-ext=\"ws\" ws-send\u003e\u003c/div\u003e\nstring = #html(div(attributes: [.htmx(.ext(\"ws\")), .htmx(.ws(.send(true)))]))\n\n```\n\n\u003c/details\u003e\n\n\n## Benchmarks\n\n- Libraries tested\n  - [BinaryBuilds/swift-html](https://github.com/BinaryBirds/swift-html) v1.7.0 (patched version [here](https://github.com/RandomHashTags/fork-bb-swift-html))\n  - [sliemeobn/elementary](https://github.com/sliemeobn/elementary) v0.4.1\n  - [JohnSundell/Plot](https://github.com/JohnSundell/Plot) v0.14.0\n  - [tayloraswift/swift-dom](https://github.com/tayloraswift/swift-dom) v1.1.0 (patched version [here](https://github.com/RandomHashTags/fork-swift-dom))\n  - [RandomHashTags/swift-htmlkit](https://github.com/RandomHashTags/swift-htmlkit) v0.10.0 (this library)\n  - [pointfreeco/swift-html](https://github.com/pointfreeco/swift-html) v0.4.1\n  - [robb/Swim](https://github.com/robb/Swim) v0.4.0\n  - [vapor-community/HTMLKit](https://github.com/vapor-community/HTMLKit) v2.8.1\n  - [dokun1/Vaux](https://github.com/dokun1/Vaux) v0.2.0 (patched version [here](https://github.com/RandomHashTags/fork-Vaux); custom renderer [here](https://github.com/RandomHashTags/swift-htmlkit/blob/main/Benchmarks/Benchmarks/Vaux/Vaux.swift))\n\nTest machine:\n- CPU: 7800x3D\n- RAM: 32GB DDR5\n- Storage: 1TB NVME (661.2 GiB free space)\n- OS: Arch Linux 6.11.9-arch1-1\n- Swift: 6.0.2, Swift 6 compiler\n\nBenchmark command: `swift package --allow-writing-to-package-directory benchmark --target Benchmarks --metric throughput --format jmh`\n\n### Static\n\n\u003cimg src=\"Benchmarks/img/throughput_static.png\"\u003e\n\n### Dynamic\n\n\u003cimg src=\"Benchmarks/img/throughput_dynamic.png\"\u003e\n\n### Conclusion\n\nThis library is the clear leader in performance \u0026 efficiency. Static webpages offer the best performance, while dynamic pages still tops the charts (I am actively researching and testing improvements for dynamic pages).\n\n## Contributing\n\nContributions are always welcome. See [CONTRIBUTIONS.md](https://github.com/RandomHashTags/swift-htmlkit/blob/main/CONTRIBUTING.md) for best practices.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frandomhashtags%2Fswift-htmlkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frandomhashtags%2Fswift-htmlkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frandomhashtags%2Fswift-htmlkit/lists"}