---
title: 1amageek/swift-html
framework: Swift Package Catalog
role: article
path: packages/1amageek/swift-html
---

# 1amageek/swift-html

SwiftHTML is a low-level declarative HTML engine for Swift applications.

## Status

SwiftHTML is an early pre-`1.0` package extracted from SwiftWeb. The public API is intended to be small and framework-neutral, but runtime and hydration contracts may still evolve before `1.0`.

This README describes the current `main` branch. Use the README from a matching Git tag when depending on a tagged release.

| Package | Role | |---|---| | `SwiftHTML` | HTML DSL, rendering, diffing, state, environment, CSS, hydration contracts, browser command contracts, and the `#Preview` macro / `HTMLPreview` Xcode preview surface. | | `SwiftHTMLClientRuntime` | Static client HTML tree and DOM host contract that can compile under standard WASM and Embedded Swift compiler profiles. | | `SwiftHTMLPreview` | Compatibility re-export of `SwiftHTML` (the preview surface now lives in `SwiftHTML`). | | Higher-level server package | HTTP routing, request/response integration, security middleware, server action gateway. | | Higher-level UI package | Design-system components, visual defaults, JavaScriptKit adapter, WASM bootstrap. |

## Requirements

SwiftHTML uses the pinned Swift 6.4 development snapshot `swift-6.4.x-DEVELOPMENT-SNAPSHOT-2026-07-17-a`. Native, standard WASM, and Embedded WASM builds use toolchain and SDK artifacts from that same snapshot.

| Platform | Minimum | |---|---:| | macOS | 15 | | iOS | 18 | | tvOS | 18 | | watchOS | 11 | | visionOS | 2 |

## Installation

Add SwiftHTML to a Swift Package with a version-based dependency:

```swift // swift-tools-version: 6.4 import PackageDescription

let package = Package(     dependencies: [         .package(url: "https://github.com/1amageek/swift-html.git", from: "0.15.0"),     ],     targets: [         .target(             name: "App",             dependencies: [                 .product(name: "SwiftHTML", package: "swift-html"),             ]         ),     ] ) ```

`#Preview` ships with `SwiftHTML`, so any target that depends on `SwiftHTML` can write previews — no separate preview product is required. `SwiftHTMLPreview` remains as a compatibility re-export for existing `import SwiftHTMLPreview` code.

## Quick Start

```swift import SwiftHTML

struct HomeDocument: HTMLDocument {     var head: some Component {         meta(.charset("utf-8"))         title("SwiftHTML")     }

var body: some Component {         main(.class("page")) {             h1("SwiftHTML")             p("Typed HTML rendered from Swift values.")             a(.href("/docs")) {                 "Read the docs"             }         }     } }

let html = HomeDocument().render() print(html) ```

`HTMLDocument` separates the document shell from nestable components:

```text HTML ├─ HTMLDocument    <!doctype html> + html + head + body └─ Component    ├─ custom components    └─ tags / text / builder output ```

A document conforms to `HTML`, so it uses the same renderer, but it does not conform to `Component`. The result builder therefore prevents accidentally nesting a complete document inside a component or element.

Custom components expose `var content: some Component`. `ComponentBuilder` lowers that authored tree to stable `ComponentContent` storage before rendering; application code does not declare fragment protocols or store fragment values. Complete documents keep the familiar `head` and `body` sections without colliding with a component's authored property.

Generic builders such as `ForEach`, modifiers, and environment modifiers retain their typed authoring APIs while lowering their executable storage to `ComponentContent`. This keeps the public DSL strongly typed and prevents the Standard WASM runtime from having to recover cross-module generic field metadata during rendering.

## Copyable Snippets

The snippets below are intentionally complete enough to paste into a Swift file. They include imports, model values, components, and the render or preview entry point. The examples use fenced Markdown code blocks so documentation surfaces can expose their normal copy action.

### Server-Rendered Page

```swift import SwiftHTML

struct ArticleSummary: Sendable {     let id: String     let title: String     let excerpt: String     let href: String }

struct ArticleListDocument: HTMLDocument {     let articles: [ArticleSummary]

var head: some Component {         meta(.charset("utf-8"))         title("Latest Articles")     }

var body: some Component {         main(.class("article-list")) {             h1("Latest Articles")             p(.class("lead"), text: "Rendered on the server with typed SwiftHTML components.")

section(.aria("label", "Articles")) {                 ForEach(articles, id: \.id) { summary in                     articleCard(summary)                 }             }         }         .style {             .maxWidth("720px")             .margin("0 auto")             .padding("32px")             .font("16px -apple-system, BlinkMacSystemFont, sans-serif")         }     }

private func articleCard(_ summary: ArticleSummary) -> some Component {         article(.class("article-card")) {             h2 {                 a(.href(summary.href)) {                     summary.title                 }             }             p(summary.excerpt)         }         .style {             .padding("16px 0")             .border("0 solid color-mix(in srgb, CanvasText 16%, transparent)")             .custom("border-bottom-width", "1px")         }     } }

func renderArticleListPage() -> String {     ArticleListDocument(         articles: [             ArticleSummary(                 id: "swift-html",                 title: "Typed HTML in Swift",                 excerpt: "Use lowercase tags, typed attributes, and components to build HTML documents.",                 href: "/articles/swift-html"             ),             ArticleSummary(                 id: "hydration",                 title: "Hydration Contracts",                 excerpt: "Render artifacts carry state, event, and browser-neutral runtime metadata.",                 href: "/articles/hydration"             ),         ]     )     .render() } ```

### Xcode Preview

`import SwiftHTML` and mark the HTML you want to inspect with `#Preview`. It renders the content in a `WKWebView` inside Xcode's canvas — a single import, no `#if DEBUG` guard, no SwiftUI, and nothing links into a release or WebAssembly build:

```swift import SwiftHTML

#Preview {     main(.class("dashboard-shell")) {         header(.class("dashboard-header")) {             p(.class("eyebrow"), text: "SwiftHTML Preview")             h1("Release Operations")             p("Inspect layout, copy, and CSS directly in Xcode.")         }

section(.class("metric-grid"), .aria("label", "Release metrics")) {             article(.class("metric-card")) {                 p(.class("metric-label"), text: "Tests")                 strong("108")                 span(.class("metric-trend"), text: "passing")             }

article(.class("metric-card")) {                 p(.class("metric-label"), text: "Preview")                 strong("Ready")                 span(.class("metric-trend"), text: "WebKit")             }         }     } } ```

### Stateful Runtime Check

```swift import SwiftHTML

struct InlineCounter: ClientComponent {     @State private var count = 0

var content: some Component {         button(.type(ButtonType.button), .onClick {             count += 1         }) {             "Count \(count)"         }     } }

func renderCounterAfterOneClick() throws -> String {     var runtime = try BrowserHydrationRuntime(         root: InlineCounter(),         host: BrowserDOMCommandBuffer(),         stateStore: StateStore()     )

guard let handler = runtime.session.artifact.clientHandlers.handlers.first else {         return runtime.session.artifact.html     }

let update = try runtime.invoke(handlerID: handler.id)     return update.html } ```

## Xcode Preview

`import SwiftHTML` and use `#Preview` to inspect SwiftHTML directly inside Xcode's canvas. There is no SwiftUI dependency, and the whole surface is self-gated behind `#if DEBUG && canImport(WebKit)`, so it never links WebKit or DeveloperToolsSupport into a release server or a WebAssembly build.

| Product | Use | |---|---| | `SwiftHTML` | HTML DSL, render artifacts, CSS, state, hydration contracts, and the `#Preview` macro. | | `SwiftHTMLPreview` | Compatibility re-export of `SwiftHTML`. New code can import `SwiftHTML` directly. |

```mermaid flowchart LR   Source["SwiftHTML content"] --> Macro["#Preview"]   Macro --> Renderer["HTMLPreviewRenderer"]   Renderer --> Document["Preview HTML document"]   Document --> WebKit["WKWebView"]   WebKit --> Canvas["Xcode canvas (PreviewRegistry)"] ```

### Basic Preview

A single `import SwiftHTML` provides both the content DSL and the `#Preview` macro — Xcode's canvas discovers it by name, exactly like SwiftUI's. No `import DeveloperToolsSupport`, no `#if DEBUG`, and no platform guard is needed:

```swift import SwiftHTML

#Preview {     main(.class("dashboard-shell")) {         header(.class("dashboard-header")) {             p(.class("eyebrow"), text: "SwiftHTML Preview")             h1("Release Operations")             p("Inspect layout, copy, and CSS directly in Xcode.")         }

section(.class("metric-grid"), .aria("label", "Release metrics")) {             article(.class("metric-card")) {                 p(.class("metric-label"), text: "Tests")                 strong("108")                 span(.class("metric-trend"), text: "passing")             }         }     } } ```

### Named Previews and Styling

Pass a display name as the first argument, exactly like SwiftUI's `#Preview`. The preview renders with a default stylesheet; apply your own CSS with SwiftHTML's element-level `.style { ... }`:

```swift import SwiftHTML

#Preview("Japanese") {     article {         h2("SwiftHTML")         p("Xcode Preview で HTML を確認できます。")     }     .style {         .padding("32px")         .font("16px -apple-system, BlinkMacSystemFont, sans-serif")         .border("1px solid color-mix(in srgb, CanvasText 16%, transparent)")     } } ```

For rendering SwiftHTML to a `WKWebView` outside a preview (custom stylesheet, language, base URL, render options), use the `HTMLPreview(...)` function directly.

### Build Behavior

`#Preview` here is SwiftHTML's own macro. Xcode's canvas discovers previews by the macro name `Preview`, so it appears in the canvas exactly like SwiftUI's `#Preview`. It expands to a `DeveloperToolsSupport.PreviewRegistry` conformance — the same type Apple's `#Preview` generates — but wrapping a `WKWebView` instead of a SwiftUI view. The expansion is self-gated behind `#if DEBUG && canImport(WebKit)`, so it is absent from release and WebAssembly builds without any guard in your own code, and the macro plugin is a host-only build tool not linked on non-Apple platforms.

### Known limitation: the canvas blanks after a few seconds

Xcode's canvas hosts the preview off the normal on-screen window, so WebKit treats the `WKWebView` as non-visible and purges its rendered content after a few seconds — the preview renders, then goes blank (the WebContent process stays alive, so this is not a crash). This affects any WebKit- or MapKit-backed preview, including Apple's own `#Preview { WKWebView() }` and `#Preview { MKMapView() }`; it is not specific to SwiftHTML, and there is no public API to prevent WebKit's non-visible purge. Use the preview for a quick look at layout and CSS; for sustained or interactive viewing, run on a device or Simulator, or serve the HTML in a browser.

SwiftHTML escapes text and attribute values by default:

```swift let rendered = div(.id("root")) {     "5 > 3 & 2 < 4" } .render() ```

## Core Concepts

| Concept | API | Notes | |---|---|---| | HTML primitive | `div`, `span`, `input`, `text`, `rawHTML`, `Element` | Lowercase types map to DOM tags. | | Document | `HTMLDocument` | A complete document with explicit `head` and `body` sections. | | Component | `Component` | Nestable HTML content. Custom components return their child tree through `content`. | | Server-owned component | `ServerComponent` | SSR/default ownership boundary. | | Client-owned component | `ClientComponent` | Owns `@State`, event closures, and hydration metadata. | | Render result | `RenderArtifact` | Public facade for HTML, diagnostics, manifests, handlers, and snapshots. | | Runtime state | `StateStore` | Component-scoped state slots used during render and hydration. | | Runtime state snapshot | `StateStoreSnapshot` | Codable state payload guarded by a state schema hash for HMR and WASM runtime swaps. | | Runtime schema | `StateSchema` | Stable hash derived from state slots, value types, and source locations. |

SwiftHTML keeps the raw render graph internal. Public code should use `RenderArtifact`, `HTMLDOMSnapshot`, hydration indexes, diagnostics, and patch/runtime records instead of constructing graph nodes.

## HTML DSL

HTML tags are lowercase Swift types. Text can be written directly inside builders, or through text initializer shortcuts:

```swift section(.id("intro")) {     h2("Client Counter")     p(.class("lead"), text: "State can belong to a ClientComponent.")     input(         .type(InputType.email),         .name("email"),         .placeholder("hello@example.com"),         .required     ) } ```

Attributes are typed where it matters and still allow escape hatches:

```swift a(     .href("/account"),     .data("tracking-id", "account-link"),     .aria("label", "Open account") ) {     "Account" }

Element("custom-element", attributes: [     .attribute("part", "label") ]) {     "Custom element content" } ```

Builder control flow works with `if`, `switch`, `for`, and `ForEach`:

```swift struct Menu: Component {     let items: [String]     let isSignedIn: Bool

var content: some Component {         nav {             ul {                 ForEach(items, id: \.self) { item in                     li {                         a(.href("/\(item)")) {                             item                         }                     }                 }             }

if isSignedIn {                 button(.type(ButtonType.button)) {                     "Sign out"                 }             }         }     } } ```

## Rendering

Use `render()` when only the HTML string is needed:

```swift let html = HomePage().render() ```

Use `renderArtifact()` when a server or runtime needs diagnostics, hydration metadata, event handlers, or a DOM snapshot:

```swift let artifact = HomePage().renderArtifact()

print(artifact.html) print(artifact.diagnostics) print(artifact.hydration.components) print(artifact.browserHydrationIndex()) ```

`HTMLRenderOptions` controls diagnostic capture, handler closure capture, browser hydration markers, and component environment overrides.

## CSS

Inline styles use `Style` and `@StyleBuilder`:

```swift div {     "Panel" } .style {     .display("grid")     .gridTemplateColumns("1fr auto")     .gap("12px")     .whiteSpace("nowrap")     .custom("--panel-tone", "muted") } ```

Stylesheets use `Stylesheet`, `CSSRule`, and `@StylesheetBuilder`:

```swift let stylesheet = Stylesheet {     rule(".panel") {         .minHeight("36px")         .background("var(--panel-background)")         .borderRadius("8px")     }

rule(".panel[data-active=\"true\"]") {         .outline("2px solid var(--accent)")     } }

print(stylesheet.cssText) ```

The generated CSS property surface is based on `@mdn/browser-compat-data`. Standard-track, non-deprecated, non-vendor properties are exposed as `Style` helpers so editors can autocomplete the CSS property surface.

`Style.custom(_:_:)`, dynamic CSS members, `CSSSelector`, `CSSRule`, and raw `style` attributes serialize values as authored. Do not pass untrusted external input directly into those APIs.

## State And Hydration

`ClientComponent` can own `@State` and event closures:

```swift struct Counter: ClientComponent {     @State private var count = 0

var content: some Component {         button(.type(ButtonType.button), .onClick {             count += 1         }) {             "Count \(count)"         }     } } ```

Rendering records state slots and event bindings in the artifact:

```swift let store = StateStore() let artifact = Counter().renderArtifact(stateStore: store)

let component = artifact.hydration.components.first let handler = artifact.clientHandlers.handlers.first ```

State snapshots are explicit runtime data. A host can preserve client state during HMR or a component WASM swap only when the rendered state schema matches:

```swift let schemaHash = artifact.hydration.stateSchemaHash let snapshot = try store.snapshot(schemaHash: schemaHash)

let nextStore = StateStore() nextStore.restore(snapshot) ```

Only values that can be encoded by the runtime are included in the snapshot. Non-encodable state falls back to the component initializer on restore.

The in-package hydration runtime can be used by tests or host adapters:

```swift let host = BrowserDOMCommandBuffer() var runtime = try BrowserHydrationRuntime(     root: Counter(),     host: host,     stateStore: StateStore() )

let handlerID = runtime.session.artifact.clientHandlers.handlers[0].id let update = try runtime.invoke(handlerID: handlerID)

print(update.commands) ```

`HydrationRuntimeSession.flush()` tracks dirty components, then currently performs a whole-root re-render and graph diff. Scoped subtree diffing is a runtime optimization boundary, not part of the current correctness contract.

## Environment

Environment values can be defined with `EnvironmentKey`:

```swift struct LocaleKey: EnvironmentKey {     static let defaultValue = "en" }

extension EnvironmentValues {     var locale: String {         get { self[LocaleKey.self] }         set { self[LocaleKey.self] = newValue }     } }

struct LocaleLabel: Component {     @Environment(\.locale) private var locale

var content: some Component {         span {             locale         }     } } ```

Type-based environment reads are optional:

```swift struct LibraryReader: Component {     @Environment(Library.self) private var library: Library?

var content: some Component {         if let library {             span {                 library.title             }         } else {             span {                 "Library unavailable"             }         }     } } ```

## Actions

SwiftHTML defines transport-neutral action contracts. It can render an action target and hidden fields, but it does not dispatch HTTP requests or invoke server actors.

```swift struct SaveAction: ActionRepresentable {     let path = "/actions/save"     let method = FormMethod.post     let fields = [         ActionField("scope", "profile")     ] } ```

Higher-level packages can map `ActionRepresentable` to forms, buttons, fetch requests, server action gateways, or actor invocation.

## Client Runtime Compiler Profiles

`SwiftHTMLClientRuntime` is the production client runtime surface. It is named by responsibility, not by compiler profile: the same source must compile under the standard WASM SDK and the Embedded Swift WASM SDK. It intentionally does not include the full `SwiftHTML` renderer, graph, Codable manifests, reflection-based component identity, or task-local render context.

```mermaid flowchart LR   A["SwiftHTMLClientRuntime static tree"] --> B["ClientDOMHost"]   B --> C["Runtime adapter"]   C --> D["Browser DOM"] ```

The package includes `Examples/EmbeddedWasm`, which verifies the compiler profile boundary by connecting `SwiftHTMLClientRuntime` to JavaScriptKit in an example package. JavaScriptKit remains outside the core SwiftHTML target.

The following historical measurement used Swift 6.3.1 and no `wasm-opt`; rerun the script with the pinned Swift 6.4 snapshot before using these numbers for a current comparison:

| Encoding | Standard WASM | Embedded WASM | Reduction | |---|---:|---:|---:| | raw | 9,027,908 bytes | 859,116 bytes | 90.5% | | gzip -9 | 2,423,702 bytes | 283,656 bytes | 88.3% | | brotli -q 11 | 1,741,470 bytes | 230,400 bytes | 86.8% |

Run the measurement locally:

```bash cd Examples/EmbeddedWasm export SWIFT_BIN="$HOME/Library/Developer/Toolchains/swift-6.4.x-DEVELOPMENT-SNAPSHOT-2026-07-17-a.xctoolchain/usr/bin/swift" ./measure-size.sh npm install npm run test:browser ```

## What SwiftHTML Does Not Own

| Concern | Expected owner | |---|---| | HTTP routing | Server framework package | | Request and response objects | Server framework package | | CSRF, CORS, Origin, Redirect policy | Server framework package | | Server action dispatch | Server framework package | | Distributed actor registry | Server framework package | | Design-system components | UI package | | JavaScriptKit DOM adapter | Runtime package | | WASM bootstrap script | Runtime package |

## Safety Notes

| Surface | Behavior | |---|---| | Text nodes | Escaped by default. | | Attribute values | Escaped and validated by attribute kind. | | URL attributes | Reject unsafe JavaScript URLs in typed URL attributes. | | `rawHTML` | Emits authored HTML; use only with trusted content. | | CSS selectors and values | Serialized as authored; validate untrusted input before passing it to CSS APIs. | | Event closures | Captured only in render artifacts for client-owned components and runtime adapters. |

## Development

```bash swift build xcodebuild test -scheme swift-html-Package -destination 'platform=macOS' -only-testing:SwiftHTMLTests node scripts/generate-swift-html-css-properties.mjs --check ```

Refresh generated CSS helpers:

```bash node scripts/generate-swift-html-css-properties.mjs ```

## Documentation

The DocC catalog lives in [Sources/SwiftHTML/SwiftHTML.docc](Sources/SwiftHTML/SwiftHTML.docc). Build it with:

```bash xcodebuild docbuild -scheme swift-html-Package -destination 'generic/platform=macOS' ```

The longer design notes live in [docs/SwiftHTML.md](docs/SwiftHTML.md).

## License

SwiftHTML is available under the MIT license.

## Package Metadata

Repository: 1amageek/swift-html

Default branch: main

README: README.md
