Introduction to WebUI
Published: Tuesday, 15 April 2025An introduction to WebUI, a Swift library for rendering HTML, CSS, and JavaScript.What is WebUI?
WebUI is a rendering library for HTML, CSS, and JavaScript, built entirely in Swift.
It offers a convenient way to create user interfaces with a syntax similar to SwiftUI, resulting in clean, efficient, and maintainable code.
Why I Built WebUI
As a frequent user of Swift for backend development, thanks to the Swift on Server workgroup, I often opted for server-side rendering of HTML within Swift to maintain a unified codebase, avoiding the complexity of a decoupled frontend. However, this approach typically involved embedding HTML templates as strings within Swift code, which introduced several challenges.
For example, consider a typical setup using Hummingbird, a Swift web framework, to render a dynamic homepage. Below is a simplified implementation of an HTML
response generator that wraps HTML content, a HomeView
struct for rendering the page’s content, and a route handler to serve the page:
import Hummingbird
/// Type wrapping HTML code.
/// Will convert to HBResponse that includes the correct content-type header
struct HTML: ResponseGenerator {
let title: String
let description: String
let isLoggedIn: Bool?
let content: String
init(
title: String,
description: String =
"Take control of your life with this wonderful todo list application.",
isLoggedIn: Bool? = false,
content: String
) {
self.title = title
self.description = description
self.isLoggedIn = isLoggedIn
self.content = content
}
public func response(from request: Request, context: some RequestContext) throws -> Response {
.init(
status: .ok,
headers: [.contentType: "text/html"],
body: .init(
byteBuffer: ByteBuffer(
string: LayoutView(
title: title,
description: description,
isLoggedIn: isLoggedIn ?? false,
content: content
)
.render()
)
)
)
}
}
/// A view that renders the marketing landing page of the application.
///
/// Displays the main value proposition, feature highlights, and appropriate call-to-action
/// buttons based on user authentication state.
///
/// - Parameters:
/// - isLoggedIn: Whether a user is currently authenticated, affecting which CTAs are shown
struct HomeView {
let isLoggedIn: Bool
init(isLoggedIn: Bool = false) {
self.isLoggedIn = isLoggedIn
}
func render() -> String {
"""
<section class="grid hero">
<div class="background"></div>
<div class="text">
<h1>
Take Control of
<span class="gradient-highlight">Your Life</span>
</h1>
<p>
With this wonderful application, designed to make your life easier while
staying out of the way. Take the first step in your new journey.
</p>
\(ActionButtons(isLoggedIn: isLoggedIn).render())
</div>
<img src="images/hero.svg" />
</section>
"""
}
}
/// Renders the home page with dynamic content based on auth status
///
/// - Parameters:
/// - request: The incoming HTTP request
/// - context: The application context
/// - Returns: The rendered HTML page
/// - Note: Content varies based on whether user is authenticated
@Sendable func home(request: Request, context: Context) async throws -> HTML {
HTML(
title: "Home",
isLoggedIn: request.cookies["SESSION_ID"] != nil,
content: HomeView(
isLoggedIn: request.cookies["SESSION_ID"] != nil
).render()
)
}
In this setup, the HomeView
struct generates HTML as a string, which is then passed to the HTML
response generator to create an HTTP response. The route handler (home
) checks for a session cookie to determine the user’s authentication status and renders the page accordingly. While functional, this approach has significant drawbacks. Storing HTML in strings sacrifices Swift’s type safety, making it impossible for the compiler to catch errors in HTML structure or attribute usage at compile time. For instance, a typo in a tag name or an unclosed tag would only be caught at runtime, often resulting in malformed HTML that’s difficult to debug. Additionally, embedding HTML strings means losing access to Swift’s powerful language features, like autocompletion, refactoring tools, and syntax highlighting, which hinders productivity and increases the likelihood of errors. Maintaining and scaling such code becomes cumbersome, especially for complex interfaces with dynamic content.
Frustrated by these limitations, I explored domain-specific languages (DSLs) for writing HTML in Swift, such as Elementary. While Elementary provided a more structured approach by allowing HTML to be expressed in Swift’s native syntax, its ergonomics and flexibility didn’t fully align with my vision for a seamless and expressive developer experience. This prompted me to create WebUI, a custom solution designed to combine the benefits of type-safe, Swift-native HTML rendering with a syntax that feels intuitive and familiar to SwiftUI.
Another goal of the project was to make it hard to write bad applications. All of the metadata generation is handled by the library, as well as the base HTML document structure. This allows you to focus on the content and just building the user interface.
Creating a Web Document
WebUI’s core functionality is generating web pages. You define a document with metadata and content, passed as a closure containing WebUI elements.
import Foundation
import WebUI
let document = Document(
metadata: Metadata(
site: "Awesome Site",
title: "Some Page",
titleSeparator: "-",
description: "This is my awesome page",
image: "/og.png",
author: "Your Name",
keywords: ["swift", "webui"],
twitter: "username",
locale: .en,
type: .website
)
) {
Header { "Hello, World!" }
Main {
Heading(level: .h1) { "This is my awesome page." }
List {
Item { "Item 1" }
Item { "Item 2" }
}
Link(to: "https://github.com/maclong9/web-ui", newTab: true) { "WebUI Repository" }
}
}
let html = document.render()
This code renders the following HTML. Currently, styles are managed with TailwindCSS, though this may evolve in future updates. Metadata is rendered in the <head>
tag, and closure content appears within the <body>
tags.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Some Page - Awesome Site</title>
<meta property="og:title" content="Some Page - Awesome Site" />
<meta name="description" content="This is my awesome page" />
<meta property="og:description" content="This is my awesome page" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:image" content="/og.png" />
<meta name="author" content="Your Name" />
<meta property="og:type" content="website" />
<meta name="twitter:creator" content="@username" />
<meta name="keywords" content="swift, webui" />
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
@theme {
--breakpoint-xs: 30rem;
--breakpoint-3xl: 120rem;
--breakpoint-4xl: 160rem;
}
</style>
</head>
<body>
<header>Hello, World!</header>
<main>
<h1>This is my awesome page.</h1>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<a
href="https://github.com/maclong9/web-ui"
target="_blank"
rel="noreferrer"
>
WebUI Repository
</a>
</main>
</body>
</html>
Adding Styles
Styles in WebUI follow a modifier pattern inspired by SwiftUI. Below is an example of a styled div with a heading. The container has a light background by default and a dark background when prefers-color-scheme: dark
is active, with typography styles applied to the heading.
import WebUI
let styledContent = Stack {
Heading(level: .h1) { "Hello, world!" }
.font(size: .xl4, weight: .bold)
}
.background(color: .zinc(._200))
.background(color: .zinc(._950), on: .dark)
Generating a Simple Static Site
Creating a Layout Component
WebUI components are defined as structs conforming to the HTML
protocol.
import WebUI
struct Layout: HTML {
let children: [any HTML]
init(@HTMLBuilder children: @escaping () -> [any HTML]) {
self.children = children()
}
public func render() -> String {
Stack {
Header {
Link(to: "/") { "Site Title" }
Navigation {
Link(to: "https://github.com/maclong9", newTab: true) { "GitHub" }
}
}
.flex(justify: .between, align: .center)
.frame(width: .screen, maxWidth: .fixed(200))
.margins(.horizontal, auto: true)
.padding()
Main {
children.map { $0.render() }.joined()
}
.flex(grow: .one)
.margins(.horizontal, auto: true)
.frame(maxWidth: .custom("95vw"))
.frame(maxWidth: .fixed(180), on: .sm)
.font(wrapping: .pretty)
.padding()
Footer {
Text {
"© \(Date().formattedYear()) "
Link(to: "/") { "Site Title" }
}
}
.font(size: .sm, color: .zinc(._600, opacity: 0.9))
.font(color: .zinc(._400, opacity: 0.9), on: .dark)
.flex(justify: .center, align: .center)
.padding()
}
.frame(minHeight: .screen)
.font(color: .zinc(._800))
.background(color: .zinc(._200))
.font(color: .zinc(._200), on: .dark)
.background(color: .zinc(._950), on: .dark)
.flex(direction: .column)
.render()
}
}
Generating the Website
To generate a static site, run the build step to create an .output
directory in the current working directory. If using Xcode, set a custom working directory in your scheme.
import WebUI
public struct StaticSite: Sendable {
var staticRoutes: [Document] {
[
Document(
path: "index",
metadata: .init(
site: "Mac Long",
title: "Home",
description: "Welcome to the home page.",
image: "/public/og.jpg",
author: "Mac Long",
type: .website
),
head: "<link rel=\"icon\" href=\"public/icon.svg\" type=\"image/svg+xml\" />",
content: {
Layout {
Heading(level: .h1) { "Home Page" }
Link(to: "/about") { "Go to About" }
}
}
),
Document(
path: "about",
metadata: .init(
site: "Mac Long",
title: "About",
description: "Learn more about us.",
image: "/public/og.jpg",
author: "Mac Long",
type: .website
),
head: "<link rel=\"icon\" href=\"public/icon.svg\" type=\"image/svg+xml\" />",
content: {
Layout {
Heading(level: .h1) { "About Page" }
Link(to: "/") { "Go to Home" }
}
}
)
]
}
func main() async throws {
try Application(routes: staticRoutes).build(assetsPath: "Sources/StaticSite/Public")
}
}
The .output
directory will contain:
.output/
index.html
about.html
You can specify a public directory to be copied to .output/public
. For example, to include an image, place it in Sources/StaticSite/Public
and reference it as:
Image(source: "public/image.jpg", description: "An image for web rendering")
Ensure the Public
directory is added as a resource in your target:
targets: [
.executableTarget(
name: "TargetName",
dependencies: [
.product(name: "WebUI", package: "web-ui")
],
resources: [
.copy("Public")
]
),
]
Conclusion
This introduction covers the basics of WebUI, a library I created to streamline web development in Swift. While still evolving, WebUI has been a valuable learning experience in Swift and web technologies. Explore this sites source code to see how a production site using WebUI is built or learn more about the library here.
Thank you for reading!