Getting started with HTML in Swift
Swift DOM is a DSL for encoding HTML or related formats, such as SVG. Generating HTML with the DSL usually involves using a subscript-assignment interface alongside a streaming interface. Read this tutorial to learn the basic patterns to use in order to get the most out of this library.
Like Swift result builders, the HTML DSL is a closure-based DSL. But unlike result builders, the library is oriented around mutating an output stream through some type-safe encoding interface. The encoder is usually denoted by $0, which is understood to be either an HTML.ContentEncoder or an HTML.AttributeEncoder, depending on context.
Swift DOM is a unidirectional HTML encoder. This means that you can only generate HTML with it; you cannot use it to parse HTML.
Entrypoints
The main entrypoint for Swift DOM is the HTML type, which wraps an output stream of UTF-8 text. You can generate a fragment of HTML using HTML.init(with:).
let fragment:HTML = .init {  $0[.section] = "Hi Barbie!" }<section>Hi Barbie!</section>To generate a complete HTML document, use the document(with:) constructor, which behaves similarly to init(with:) except it prepends the <!DOCTYPE html> declaration to the output stream.
let document:HTML = .document {  $0[.html]  {  $0[.head] { $0[.title] = "Barbie onboarding" }  $0[.body] { $0[.section] = "Hi Barbie!" }  } }You can load a string from the output buffer with string interpolation, but many applications are better-off loading the UTF-8 buffer directly from HTML.utf8.
print("\(document)")<!DOCTYPE html> <html>  <head>  <title>Barbie onboarding</title>  </head>  <body>  <section>Hi Barbie!</section>  </body> </html>Assignment patterns
The most-recoginzable pattern in Swift DOM is the subscript-assignment pattern. A snippet is worth a thousand words:
$0[.div] = "Hi Barbie!"<div>Hi Barbie!</div>The subscript-assignment pattern works with any type that conforms to HTML.OutputStreamable. Although you could also retroactively conform types such as Int to this protocol, we recommend encoding such types through string interpolation instead, as this makes the intent clearer. You might, for example, want to display integers in a different format elsewhere in your application.
$0[.div] = "\(1959)"<div>1959</div>Subscript-assignment is not idempotent
The subscript-assignment pattern is not idempotent. If you assign to a ContentEncoder’s subscript multiple times, you will generate multiple elements.
$0[.div] = "A" $0[.div] = "B" $0[.div] = "C"<div>A</div><div>B</div><div>C</div>Subscript-assignment is optional
The getters of ContentEncoder’s assignable subscripts always return nil. This means that the setters also accept nil assignments, which the encoder will always ignore. This is useful because it provides a concise syntax for eliding empty elements.
let menu:(String?, String?, String?) = ("Home", nil, "Contact") $0[.li] = menu.0 $0[.li] = menu.1 $0[.li] = menu.2<li>Home</li><li>Contact</li>Encoding attributes
You can add attributes to an element by passing a trailing closure to the subscript. Unlike elements, attributes use property syntax instead of subscript syntax.
If you assign nil to a subscript with attributes, the encoder will not execute the closure.
let age:(String?, String?) = ("5 hours ago", nil) $0[.span] { $0.class = "timeinterval" } = age.0 $0[.span] { $0.class = "timeinterval" } = age.1<span class='timeinterval'>5 hours ago</span>Like element encoding, attribute encoding is not idempotent. The encoder will always emit attributes in the order they were assigned, and it will not prevent you from emitting the same attribute multiple times.
$0[.button] {  $0.class = "premium"  $0.class = "area"  $0.type = "submit" } = "Purchase"<button class='premium' class='area' type='submit'>Purchase</button>Encoding attributes without values
You can encode attributes without values by assigning a boolean value to the attribute’s setter. The attribute will only appear in the output if the value is true.
$0[.input] { $0.disabled = true ; $0.type = "text" } $0[.input] { $0.required = false ; $0.type = "text" }<input disabled type='text'><input type='text'>Encoding attributes with special characters
Swift DOM always escapes special characters in attribute values.
$0[.input] { $0.placeholder = "Hawai'i" ; $0.type = "text" }<input placeholder='Hawai'i' type='text'>Unicode characters are not “special” just because they are Unicode. If you spell Hawaiʻi with the ʻokina, the encoder will not escape it.
$0[.input] { $0.placeholder = "Hawaiʻi" ; $0.type = "text" }<input placeholder='Hawaiʻi' type='text'>Encoding attributes dynamically
Although it is an exceptionally rare use case, you can encode attributes dynamically by passing instances of HTML.Attribute to subscript(name:).
A slightly more common use case is to encode data- attributes using subscript(data:).
let likes:Int = 117 $0[.span] { $0[data: "likes"] = "\(likes)" } = "\(likes) likes"<span data-likes='117'>117 likes</span>Encoding nested elements
You can encode nested markup by passing an additional trailing closure to the subscript. If you are also encoding attributes, you must use content: to label the closure.
$0[.ol] {  $0.id = "barbieland-constitution" }  content: {  $0[.li]  {  $0[.h2] = "Article I"  $0[.p] = "Executive branch"  }  $0[.li]  {  $0[.h2] = "Article II"  $0[.p] = "Legislative branch"  }  $0[.li]  {  $0[.h2] = "Article III"  $0[.p] = "Judicial branch"  } }<ol id='barbieland-constitution'>  <li>  <h2>Article I</h2>  <p>Executive branch</p>  </li>  <li>  <h2>Article II</h2>  <p>Legislative branch</p>  </li>  <li>  <h2>Article III</h2>  <p>Judicial branch</p>  </li> </ol>Some users may prefer the equivalent syntax that does not use labeled closures.
$0[.ol, { $0.id = "barbieland-constitution" }] {  $0[.li]  {  $0[.h2] = "Article I"  $0[.p] = "Executive branch"  }  $0[.li]  {  $0[.h2] = "Article II"  $0[.p] = "Legislative branch"  }  $0[.li]  {  $0[.h2] = "Article III"  $0[.p] = "Judicial branch"  } }Encoding empty elements
Unlike the subscript-assignment pattern, the nested closure syntax always generates the container element passed to the subscript, even if nothing is written inside the closure.
$0[.ol, { $0.id = "barbieland-constitution" }] { _ in }<ol id='barbieland-constitution'></ol>Because Swift DOM distinguishes between void and container elements at compile-time, you can safely omit the trailing braces when encoding empty elements.
$0[.ol] { $0.id = "barbieland-constitution" }Encoding nested elements with elision
As a syntactic convenience, Swift DOM allows you to use optional subscript assignment with multiple layers of single-element wrappers.
$0[.ol] {  $0[.li, .p] { $0.class = "warning" } = "Do not feed the humans."  $0[.li, .p] { $0.class = "warning" } = nil as Never? }<ol>  <li class='warning'><p>Do not feed the humans.</p></li> </ol>This syntax passes through the variadic subscript(_:_:exterior:) overload, which applies any associated attributes to the outermost element.
Encoding unsafe content
Some HTML elements are inherently unsafe to encode, as their text content must satisfy rules that are not easily enforced at compile-time. There are two such elements in HTML.
You can encode such elements using the subscript(unsafe:_:) interface.
Streaming patterns
The HTML DSL provides a streaming interface alongside the subscript-assignment interface.
Streaming values
Most prose rendering involves interpolating text nodes with HTML elements. The basic interface to use for interpolated streaming is the +=(_:_:) operator.
let notifications:Int = 5 $0[.p] {  $0 += "Welcome Barbie! You have "  $0[.span] { $0.class = "notifications" } = "\(notifications)"  $0 += " unread notifications." }<p>Welcome Barbie! You have <span class='notifications'>5</span> unread notifications.</p>Streaming values with elision
Like the subscript-assignment interface, the streaming interface supports elision with the ?=(_:_:) operator.
let notice:String? = nil $0[.p] { $0 ?= notice }<p></p>Streaming existentials
For performance reasons, most of Swift DOM’s encoding interfaces are generic. You can stream existentials by using the *=(_:_:) operator.
let message:any HTML.OutputStreamable = "string" $0[.p] { $0 *= message }<p>string</p>There is no operator for streaming optional existentials. You will need to use normal control-flow statements to handle optional existentials.