Version 1.0
Native Markup Language (NML) is a markup language derived from SGML, designed to represent composable UI frameworks. This specification defines the conversion from SwiftUI to NML.
When serving NML documents:
Request Headers:
ACCEPT: application/swiftui
Response Headers:
CONTENT-TYPE: application/swiftui
NML documents must begin with a doctype declaration. For SwiftUI documents:
<!doctype swiftui>
<nml>
<head>
<Style url="/styles/main.nss"/>
</head>
<body>
<Text>Hello World</Text>
</body>
</nml>
The doctype declaration is used for document annotation only and is ignored by parsers.
NML documents require the following structure:
<nml>
<head>
<Style url="/styles/main.nss"/>
</head>
<body>
<!-- Application views go here -->
<NavigationStack>
<Text>Hello World</Text>
</NavigationStack>
</body>
</nml>
The head section contains:
- Style references using the
Style
tag - Template definitions for lifecycle views
- Other client configuration elements
Example of style inclusion:
<head>
<Style url="/base.nss"/>
<Style url="/components.nss"/>
<Style url="/theme-dark.nss"/>
</head>
The body
tag is required and must contain the application's view hierarchy:
<body>
<TabView>
<VStack tabItem="Home">
<Text>Welcome to the Home Tab</Text>
<Button>Get Started</Button>
</VStack>
<ScrollView tabItem="Settings">
<VStack spacing="16">
<Toggle isOn="true">Dark Mode</Toggle>
<Toggle isOn="false">Notifications</Toggle>
</VStack>
</ScrollView>
</TabView>
</body>
Clients can define views for different UI lifecycle states in the head section:
<head>
<VStack template="disconnected" spacing="16">
<Image source="cloud.slash"/>
<Text style="font(.headline)">Connection Lost</Text>
<Text style="foregroundStyle(.secondary)">
Attempting to reconnect...
</Text>
<ProgressView/>
</VStack>
</head>
<head>
<VStack template="reconnecting" spacing="16">
<ProgressView style="progressViewStyle(.circular)"/>
<Text>Reconnecting...</Text>
<Button style="buttonStyle(.bordered)">
Cancel
</Button>
</VStack>
</head>
<head>
<VStack template="error" spacing="16">
<Image source="exclamationmark.triangle.fill"/>
<Text style="font(.headline)">An Error Occurred</Text>
<Text style="foregroundStyle(.secondary)">
Unable to complete the requested operation
</Text>
<Button style="buttonStyle(.bordered)">
Retry
</Button>
</VStack>
</head>
SwiftUI views convert directly to NML elements with exact case preservation of the view name:
LazyVStack(alignment: .leading) {
Text("Hello")
ScrollView(.horizontal) {
Text("World")
}
}
Converts to:
<LazyVStack alignment="leading">
<Text>Hello</Text>
<ScrollView axis="horizontal">
<Text>World</Text>
</ScrollView>
</LazyVStack>
View initialization arguments become element attributes with exact case preservation:
NavigationLink(destination: DetailView(), isActive: true, label: "Settings")
.buttonStyle(.bordered)
Converts to:
<NavigationLink destination="DetailView" isActive="true" label="Settings">
The values maintain their string representation, but the client infers:
isActive="true"
asBool
becauseisActive
isBool
in SwiftUIlabel="Settings"
asString
becauselabel
isString
in SwiftUIdestination="DetailView"
asView
becausedestination
isView
in SwiftUI
Unlike HTML, NML requires explicit boolean values for attributes:
Toggle("Light", isOn: true)
Toggle("Dark", isOn: false)
Converts to:
<Toggle isOn="true">Light</Toggle>
<Toggle isOn="false">Dark</Toggle>
The following are invalid in NML:
<!-- Invalid: boolean attribute without value -->
<Toggle isOn>Light</Toggle>
<!-- Invalid: omitted boolean attribute -->
<Toggle>Light</Toggle>
- All attribute values are strings
- Special characters must be encoded using HTML character entities:
"
becomes"
&
becomes&
<
becomes<
>
becomes>
JSON encoding is only allowed for arrays and lists in attribute values:
Text(items: [1, 2, 3])
Converts to:
<Text items="[1,2,3]"/>
The id
modifier is unique in that it's the only modifier that converts to an attribute rather than being included in the style attribute:
Text("Hello")
.id("greeting")
.padding()
Converts to:
<Text id="greeting" style="padding()">Hello</Text>
SwiftUI view modifiers convert to style attributes, with member expressions retaining their leading dots:
Text("Hello")
.padding()
.foregroundStyle(.green)
Converts to:
<Text style="padding(); foregroundStyle(.green)">Hello</Text>
View builder closures in modifiers use template references. Templates must be defined as direct children of the element that references them, and template names must only be unique among siblings:
VStack {
RoundedRectangle(cornerRadius: 8)
.overlay(alignment: .topLeading) {
Star(color: .red)
}
Circle()
.overlay {
Star(color: .blue)
}
}
Converts to:
<VStack>
<RoundedRectangle
cornerRadius="8"
style="overlay(alignment: .topLeading, content: :starOverlay)"
>
<Star color="red" template="starOverlay"/>
</RoundedRectangle>
<Circle style="overlay(content: :starOverlay)">
<Star color="blue" template="starOverlay"/>
</Circle>
</VStack>
The attr
helper provides a way to reference element attribute values within style declarations:
VStack {
}
.navigationTitle("Home")
Converts to:
<VStack navTitle="Home" style="navigationTitle(attr(:navTitle))">
Multiple attr references can be used within the same style string:
Circle()
.fill(.blue)
.frame(width: 100, height: 100)
.padding(.horizontal, 20)
Converts to:
<Circle
fillColor="blue"
frameWidth="100"
frameHeight="100"
padAmount="20"
style="fill(attr(:fillColor)); frame(width: attr(:frameWidth), height: attr(:frameHeight)); padding(.horizontal, attr(:padAmount))"
/>
Similar to HTML, NML supports extracting styles into separate stylesheets that can be referenced using class names:
Button("Submit") {
}
.padding()
.background(.blue)
.foregroundStyle(.white)
Can be represented using a class reference:
<Button class="primary-button">Submit</Button>
The definition and structure of stylesheets are defined in the Native Stylesheet (NSS) specification document.
SwiftUI:
/// Creates a toggle that generates its label from a string.
///
/// - Parameters:
/// - title: A string that describes the purpose of the toggle.
/// - isOn: A binding to a Boolean value that determines the toggle's state.
public init(_ title: String, isOn: Binding<Bool>)
SwiftUI usage:
Toggle("Switch Theme", isOn: $isDarkMode)
Converts to:
<Toggle isOn="true">Switch Theme</Toggle>
SwiftUI:
extension Button where Label == Text {
/// Creates a button that displays a text label.
public init(_ title: String, action: @escaping () -> Void)
/// Creates a button that displays a localized text label.
public init(_ titleKey: LocalizedStringKey, action: @escaping () -> Void)
}
SwiftUI usage:
Button("Save") {
saveData()
}
Button("welcome.message") {
showWelcome()
}
Converts to:
<Button>Save</Button>
<Button localized="true">welcome.message</Button>
- Elements must be properly nested and closed
- Template names must be unique only among sibling elements
- Templates must be defined as direct children of the element referencing them
- All attribute values must be properly encoded
- Member expressions within style values must retain their leading dot (e.g.,
.green
), while attribute values on elements must not have leading dots (e.g.,alignment="leading"
) - Only arrays and lists may be JSON-encoded in attribute values
- All attr references must point to existing attributes on the current element
- Boolean attributes must have explicit values
- Application views must be contained within the body tag
- Lifecycle templates must be defined in the head section
- File extension:
.nml
- Encoding: UTF-8
- Whitespace is preserved within text content